The module for this lesson is still under development. Please contact us if you have any questions.

 

Exception Handling-“What Can Go Wrong Will Go Wrong” -CS2

 

Read Background
Execute Lab Assignment
Complete Security Checklist
Answer Discussion Questions

Background

top

Summary:

During the execution of a program, things can, and do go wrong. The user may enter inappropriate data, a device may go offline, necessary disk space for output may not be available, etc. Errors occurring at runtime are known as exceptions and exception handling is the process designed to handle exceptions. Failure to properly handle an exception can cause a program to abort and/or expose sensitive information about the program and the environment in which the program is executing.

Description:

When an exception occurs, C++ creates an exception object containing information about the error and the program state at the time of the error. There are numerous (about 20) exception classes defined in the C++ libraries.

Once the exception object has been constructed, it is handed to the C++runtime system. This is called ‘throwing’ an exception. The C++runtime system must then locate an exception handler. In searching for an exception handler, the runtime system begins with the method in which the exception occurred. If this method is not prepared to handle the exception, the runtime system will search the call stack in reverse in search of a suitable exception handler. For example, if methodA calls methodB which calls methodC and an exception occurs during the execution of methodC, the runtime system will search methodC first, then methodB, and lastly methodA for an exception handler. When an appropriate exception handler is found, the runtime system turns over the exception object to the handler. The selected exception handler is said to ‘catch’ the exception.

In addition to special exception objects, C++ allows any object or primitive data type to be thrown. While these data might not contain useful information about the error or program state like an exception object, they can be caught in exactly the same way. This allows programmers to code simple custom exception-like flow control within their own programs, without having to create their own class inheriting an existing exception class.

Risk – How Can It Happen?

In some languages (but not C++), certain exceptions are known as checked exceptions and exception handling, or catching checked exceptions, is required to be included in the program. Other exceptions (and all exceptions in C++) are known as unchecked exceptions, which do not require an exception handler. Most unchecked exceptions, however, cause the program to display an error message (which may be a security issue since it exposes the inner details of your application) and stop execution when not handled. Others, such as problems with input/output, must be activated by the programmer before the runtime will throw them. Exception handling provides a way for the program to possibly recover from an error or at least terminate in a graceful manner.

Code Responsibly – How to Handle Exceptions?

  1. Know your exceptions: Every method in the standard C++ library has documentation describing when any exceptions might be thrown. Although unchecked exceptions do not require exception handling code, consider handling them or providing code checks such that the exception can never occur.
  2. Order exception handlers correctly: When more than one exception handler is desired, code them in the order that they are more likely to occur. When a generic exception handler is also used, it should appear last.
  3. Handle exceptions appropriately: Once an exception occurs, the remaining statements of the try block will not be executed. Therefore, it is important that the exception handler recover from the exception such that the program can continue. If recovery in not possible, exit the program.
  4. An exception should not expose sensitive information: Some exception objects define messages that expose information about the state of the program and/or the file system. Therefore, be careful when allowing default exception behavior or when using an exception’s what() method.
  5. runtime_error exceptions are usually thrown due to bugs in the program. These bugs should be corrected during development.

Lab Assignment

top

Program 1

#include 

using namespace std;

int main() {
    int num = 0;
    
    do {
        cout << "Enter a number between 1 and 10" << endl;
        cin >> num;

        if(num < 1 || num > 10) {
            cout << "Illegal value, " << num << " entered. Please try again." << endl;
        }
    } while (num < 1 || num > 10);
    
    cout << "Value " << num << " correctly entered! Thank you." << endl;
}

Lab Questions:

    1. Type* the program above and compile. Run and enter an integer between 1 and 10.
    2. The program is requesting a number, but what if the user does not cooperate? Run the program again and enter 'four'. The program is unable to store an integer value into num, so num retains its original value of 0 and the program falls into an infinite loop.
    3. By default, C++ does not throw exceptions when something goes wrong with IO. Activate exception-throwing by prepending this line of code to the start of your main method.
      cin.exceptions(iostream::failbit);

      This line informs C++ that you would like the program to throw an iostream::failure exception whenever an io-related method fails. Try running the program again and enter 'four'. This time, the program should terminate and generate an error message, rather than fall into an infinite loop.

    4. Now it's time to handle the exception we've activated. Add a try/catch block to catch and handle the iostream::failure exception. Identify the statements that cause the error as well as the portions of the program that depend upon these statements. Enclose these statements within the try block. Follow the try block with the catch block given below. Note that we pass the exception by reference. Also note that, when cin throws its exception, the input token will remain in the buffer so that it can be examined by the program. In our case, we will not be examining the token, but simply clearing out the buffer to start over.
      catch(iostream::failure& iof) {
          cout << "Non-integer value. Please enter a number." << endl;
          cin.clear();                                // reset error flags
          cin.ignore(numeric_limits::max(), '\n');    // clear buffer
      }
      

      Try running the program again and entering 'four'. If you have placed your try/catch block appropriately, the program will inform you this is an invalid value, and then it will give you the opportunity to try again.

    5. Now our program catches one type of invalid input, but is there more? Recall that the program is requesting a number between 1 and 10. Run the program again and enter 10.8. Although this number is greater than 10, the program will think you entered simply 10 and tell you it is a valid value. In fact, the program will accept any invalid input so long as it starts with an integer, leaving anything else for the next time some input is requested from the buffer.
    6. C++ does not offer an exception for this situation, so we'll have to make our own. Include the following code after you write the user's input to num.
      // if this number was part of a non-integer entry, throw an exception
      if(cin.peek() != '\n') {
          throw #
      }
      

      This code checks to see if the user pressed 'Enter' immediately following the integer we think we read. If not, we throw an integer pointer, pointing to num, as an exception. Now we have to catch it. Add this catch block just after your current catch block. You will have to #include

      .

      catch(int* pnum) {
          cout << "Non-integer value. You entered something after " << *pnum << "." << endl;
          cin.clear();                                            // reset error flags
          cin.ignore(numeric_limits::max(), '\n');    // clear buffer
          
          *pnum = 0;      // reset num to its initial value
      }
      

      Note that we are able to use the argument of the catch block to better inform the user of what went wrong. Also note that we have to reinitialize num, or else the loop might terminate prematurely.

    7. Compile and run the program again, testing with a variety of input (integers, floats, characters) The program should not abort when floats or character data is given.
    8. Complete the security checklist for Program 1.

*Copying and pasting programs may result in syntax errors and other inconsistencies. It is recommended you type each program.

 

Program 2

#include 
#include 

using namespace std;

int main() {
    int total = 0;
    int num = 0;
    
    ifstream inputFile;
    
    inputFile.open("inFile.txt");

    while(!inputFile.eof()) {   // until we have reached the end of the file
            inputFile >> num;
            total += num;
    }
    
    inputFile.close();
    
    cout << "The total value is " << total << "." << endl;
}

Lab Questions:

  1. We should start by activating IO-exceptions. Do so using the same method in Step 3 of the Program 1 assignment above, except that instead of cin, our input stream is inputFile, which is an instance of an ifstream instead of an iostream.
  2. We will now test the program knowing the input file does not exist. Run the program (making sure there isn't a file named "inFile.txt" in the same folder as your program). You should get an error message describing your runtime exception and perhaps even showing a file name containing more detailed information. If you can, try to find this file and open it with a simple text editor like Notepad. The information in this file may not mean much to you right now, but someday it might help you track down exactly what's wrong with your program when the error isn't so simple. Conversely, if experienced hackers can track this information down, they might use the information to take advantage of weak points in the security of your program. This is why it is best to find and handle any exceptions that can be thrown by your program before publishing it.
  3. We don't want to expose any sensitive information if the input file does not exist. Add a try/catch block such that if the input file is not found, the problem is reported and a total of 0 is displayed. Run the program to see if you did it correctly: only your error message should be visible, not any sensitive information about your program or file system.
  4. Create a file named inFile.txt using a plain text editor. Enter only integer values separated by spaces or new-lines into the file, and save the file in the same folder as the program (if you are using a IDE, then put it in the main project folder). Run the program to verify that the program functions correctly with a valid input file.
  5. Now change a number to an invalid value, such as 'four', and run the program again. Did the program display your "file not found" error message? The problem is that C++ uses the same exception for all of its IO problems. Depending on your compiler, the exception's what() method may provide useful information to distinguish what cause the error. Unfortunately, it may also or instead provide sensitive information that you don't want users to be able to see. Fortunately, we can nest try/catch blocks to isolate which problem caused the exception. Let's add another try/catch block inside the loop. In addition to narrowing where the exception might have occurred, this will allow our program to report the invalid data, resume processing, and produce correct results for all the valid data given afterwards. Use the following code for your catch block.
    catch(ifstream::failure& iof) {
        cout << "Skipping non-integer value, found after "<> ignore;
    }
    

    Note that since we are analyzing each number independently, we simply read in the next token to a dummy variable, rather than use the ignore() method. Run your program with a variety of invalid and valid values in your file to test how well the program works.

  6. If you selected good test values, you may have noticed that your program currently includes the integer components of invalid values like '10forward' or '4ever'. We already know how to fix this! Add the following code after reading in some value to num:
    // if this number was part of a non-integer word, throw an exception
    if( !( inputFile.eof() || isspace(inputFile.peek()) ) ) {
         throw #
    }
    

    The change in the condition from Program 1 accounts for the fact that, since we are analyzing each number at a time, it may be followed by any whitespace, rather than just '\n'. And since we are working with files, we should also check if we are at the end of the file. Now, add an appropriate catch block to handle the exception we have just added. Hint: draw from Program 1 Step 6 and Program 2 Step 5.

  7. Run your program with all sorts of values and verify that it handles all exceptions as smoothly as it should.
  8. Complete the security checklist for Program 2.

*Copying and pasting programs may result in syntax errors and other inconsistencies. It is recommended you type each program.

Security Checklist

top

Security Checklist

Vulnerability: Exception Course: CS1  
Check each line of code Completed
1. Underline each statement that may cause an exception to occur.  
For each underlined statement:  
2. Highlight any statement in the exception handler that may expose information about the program.  
3. Highlight any statement in the exception handler that may expose information about the file system.  
Highlighted areas indicate vulnerabilities!  

Discussion Questions

top
  1. With keyboard input, what types of errors may occur?
  2. An iostream::failure is an unchecked exception, and indeed must be activated before it can be thrown. Since exception handling is not required, why should the programmer add this checking?
  3. What types of exceptions may occur when working with a file? Hint, visit the Java API and select the java.io library. Review the list of exceptions included in this library.
  4. Describe other types of exceptions (non-IO related) that may occur at runtime. Hint: view the documentation for the exception class, and any of its children.
  5. If a method throws a checked exception, how can the calling method avoid coding a try/catch block to handle the exception? In what cases might the calling method use this option?
  6. Exception classes are derived from other exception classes. For example, the overflow_error class is derived from the runtime_error class. And, the runtime_error class is derived from the exception class. Rather than check for the exact exception class, the catch clause could specify a super class. What are the advantages and disadvantages of specifying a super class in a catch clause?
 
Copyright © Towson University