Error Overview

Unlike the DOS versions of PowerBASIC, Windows versions of PowerBASIC employ a completely different philosophy: to generate the smallest and fastest possible code.  Consequently, error handling is placed firmly in the hands of the programmer.  PowerBASIC does not stop your program when a run-time error occurs.  It is responsibility of the programmer to check for any conceivable errors that may occur after executing a statement.  This is especially true with disk access routines.  This section describes the types of errors that may be encountered, and follows on with a discussion on error detection and error handling techniques.

Compile-time errors

Compile-time errors are generated when the compiler cannot resolve a problem in your source code while it is compiling.  Examples include: typographical errors; assigning incorrect values to variables (such as "x$ = 5"); and attempting to use a variable name which has not been dimensioned when OPTION EXPLICIT (or #DIM ALL) has been turned on.

When a compile-time error is detected, PowerBASIC will display a message box indicating the error code, plus a brief description, along with the line number in the code where the error occurred.  The offending line of code will also be displayed.  If you are using the PowerBASIC IDE, the caret will move to the offending line once the error dialog has been dismissed.

Run-time errors

Run-time errors are generated when execution of a particular code statement or function results in an error condition being set.  Run-time errors caught by PowerBASIC include Disk access violations (i.e., trying to write data to a full disk), out of bounds array and pointer access, and Memory allocation failures.  Array bounds and null-pointer checking is only performed when #DEBUG ERROR ON is used.

Run-time errors can be trapped; that is, you can cause a designated error-handling subroutine to get control should an error occur.  Use the ON ERROR statement to accomplish this.  This routine can "judge" what to do next based on the type of error that occurs.  File-system errors (for example, disk full) in particular are prime candidates for run-time error-handling routines.  They are the only errors that a thoroughly debugged program should have to deal with.

The ERROR statement (which simulates run-time errors) can be used to debug your error-handling routines.  It allows you to deliberately cause an error condition to be flagged.  Avoid using error numbers higher than 240, as they are reserved for use in critical error situations which can never be trapped with ON ERROR. Run-time error values are restricted to the range 1 through 255, and the compiler reserves codes 0 through 150, and 241 through 255 for predefined errors.  Attempting to set an error value (with the ERROR statement) outside of the valid range 1 to 255 will result in a run-time Error 5 ("Illegal function call") instead. In addition to the predefined run-time errors, you may also set your own customized run-time error codes in the range 151 through 240.  These error codes may be useful to signal specific types of errors in your own applications, ready to be handled by your error trapping code.

In the situation where an undocumented run-time error occurs, the chief suspect is memory corruption.  This can typically be cause by writing beyond an array boundary, improper use of pointers, bad Inline Assembly code, etc.

Disk Errors

Disk and I/O errors are always trapped at run-time by PowerBASIC.  If a run-time Disk or I/O error is detected, the error code is placed in the ERR system variable.  If ON ERROR is enabled, code execution will branch to the designated local error handler.

All error handling in PowerBASIC is local to each Sub, Function, Method, and Property.  You cannot create a global error handler routine as you can in some DOS BASICs.

When an error occurs in PowerBASIC, an error code is placed into the ERR system variable.  If Error Trapping has been enabled, execution branches to the error trap.  Otherwise, execution continues.  If an error occurs and your code does not take care of it, either by using an error trap or by explicitly testing the ERR or ERRCLEAR variables, your program may produce unpredictable results.  For example, in the following code, several problems can occur which would cause the code to fail, and possibly even trigger a General Protection Fault (GPF) in Windows:

SUB ReadFile(Filnam$, buffer$(), Lines%)

  RESET Lines%

  OPEN Filnam$ FOR INPUT AS #1

  WHILE ISFALSE EOF(1)

    INCR Lines%

    LINE INPUT #1, buffer$(Lines%)

  WEND

  CLOSE #1

END SUB

Here, the ERR variable is not checked after the OPEN statement to see if it was successful.  If the file does not exist or has been locked by another process, a run-time error can occur.  In this case, EOF(1) will never be able to return TRUE (non-zero) since the file was not able to be opened, and therefore the end of the file cannot be determined.  Further, checking the EOF of a file that has not been opened will trigger yet another run-time error.

The result is that without adequate error testing, this small loop will begin to run continuously.

While certainly a flaw in the code, no harm will come to the program for period.  However, a fatal error in the LINE INPUT# statement is imminent if the Lines% variable value exceeds the UBOUND of the buffer$() array.  A fatal error could also occur if buffer$() was not previously dimensioned, or it wasn't dimensioned with enough elements to store the entire file (that is, assuming the file was opened successfully).

In these cases, a General Protection Fault (GPF) is quite likely to occur, as soon as invalid memory addresses begin to be accessed in an attempt to store the string data.  You can prevent the array boundary GPF by turning on error checking using the #DEBUG ERROR ON metastatement.  However, if the array was not previously dimensioned or does not have enough space, the code will still fail in its overall objective.

A more robust version of this example code follows:

#DEBUG ERROR ON

SUB ReadFile(Filnam$, buffer$(), Lines%)

  LOCAL Temp$

  ON ERROR RESUME NEXT

 

  RESET Lines%

  OPEN Filnam$ FOR INPUT AS #1

  IF ERR THEN                        'error opening file

    EXIT SUB

  END IF

 

  WHILE ISFALSE EOF(1)

    INCR Lines%

    LINE INPUT #1, Temp$

    IF ERR then EXIT SUB             'abort if disk error

    buffer$(Lines%) = Temp$

    IF ERR = 9 THEN                  'subscript out of range

      REDIM PRESERVE buffer$(Lines%) 'increase array size

      buffer$(Lines%) = Temp$

    END IF

  WEND

  CLOSE #1

END SUB

Numeric Errors

In order to generate tight, fast code, we have eliminated quite a bit of error checking that was done in earlier compilers (such as Division-by-Zero, Numeric Overflow, and most other numeric checking errors).  While this results in code that is considerably smaller and faster than any other Windows compiler product, it does put more of an onus on the programmer to write code that is bug-free, or code that does its own error checking and validation of its data.

For example, an application that performs exponentiation of a negative value to a fractional power (-5^1.9) will not trigger a run-time error, but the result of the expression will be undefined.  Therefore, it makes sense for the application to make some attempt to validate or restrict the numeric range of the arguments of this kind of expression.

 

 

See Also

Error Trapping