Manual |
|
Errors |
|
17. ERRORS
17.1 Parsing errors
Parsing errors are triggered by interpreter and compiler. The checks for these errors are done before the program is executed respectively compiled. The errors do not terminate parsing except for error 1 (Out of heap space). If there are errors the program cannot be interpreted respectively compiled. The interpreter option -x can be used to execute even if the program contains errors. The following parsing errors exist:
17.2 Compilation errors
The compiler does checks when generating code. As a result of the checks some warnings might be written. The warning level can be specified with the option -wn
Level Warning 2 Comparison with %s always evaluates to %s. 2 Expression raises "%s". 1 Catch of "%s" although the checks are suppressed. 1 Catch of otherwise although the checks for %s are suppressed. 1 Duplicate when values %s. 1 Previous usage of %s. 1 When value must be constant. 1 Forward defined function called. 1 Forward definition of the called function.
17.3 Exceptions
An exception is an anomalous event that arises during program execution. Exceptions change the normal flow of program execution. An exception transfers the execution to a corresponding exception handler. If no corresponding exception handler exists the program is terminated. There are various exceptions, which can be raised: MEMORY_ERROR, NUMERIC_ERROR, OVERFLOW_ERROR, INDEX_ERROR, RANGE_ERROR, FILE_ERROR, DATABASE_ERROR, GRAPHIC_ERROR and ILLEGAL_ACTION. A program can raise an exception with the raise statement. For example:
raise RANGE_ERROR;
Additional exceptions can be declared with:
const EXCEPTION: MY_ERROR is enumlit;
17.3.1 MEMORY_ERROR
The exception MEMORY_ERROR is raised if there is not enough memory to store some data. This error can be raised from the run-time library or from the interpreter kernel. Catching a MEMORY_ERROR is possible, but it must be done with care. Variables involved in a MEMORY_ERROR may have an illegal value. A MEMORY_ERROR may be raised by various operations of the following types:
- array, struct, hash, file, func, proc, reference, string.
- Additionally the interpreter kernel may raise this exception also.
17.3.2 NUMERIC_ERROR
The exception NUMERIC_ERROR is raised if a numeric operation cannot deliver a correct result. This includes several things that are mathematically undefined such as division by zero, integer exponentiation with a negative exponent, square root of a negative number and logarithm of a negative number. NUMERIC_ERROR can be raised by operations of several types:
- It may be raised from the following integer operations:
- !, **, div, rem, mdiv, mod, sqrt, log2, log10.
- It may be raised from the following bigInteger operations:
- !, **, div, rem, mdiv, mod, sqrt, log2, log10.
- It may be raised from the following rational operation:
- /.
- It may be raised from the following bigRational operation:
- /.
In detail the following conditions can cause a numeric error:
- Division (div, rem, mdiv, mod, /) by zero. E.g.: 1 div 0 raises NUMERIC_ERROR. Note that a float division by zero does not raise NUMERIC_ERROR but returns Infinity or -Infinity instead.
- Exponentiation (**) if the exponent is a negative integer. E.g.: 2 ** (-1) raises NUMERIC_ERROR.
- Functions (sqrt, log2, log10, !) that are only defined for positive arguments. E.g.: sqrt(-1) raises NUMERIC_ERROR.
17.3.3 OVERFLOW_ERROR
An integer overflow occurs if a calculation produces a result that cannot be stored in an integer variable. This happens if the result is less than integer.first or greater than integer.last.
- It may be raised from the following integer operations:
- - (sign), +, -, *, **, div, rem, mdiv, mod, <<, >>, +:=, -:=, *:=, <<:=, >>:=, !, abs, succ, pred, incr, decr.
In detail the following conditions can cause an overflow:
- Negating can overflow because in a two's complement representation there is no corresponding positive value for the most negative integer. E.g.: -integer.first raises OVERFLOW_ERROR.
- Addition, subtraction, multiplication and exponentiation (+, -, *, **, succ, pred) trigger an overflow if the result would be less than integer.first or greater than integer.last.
- Arithmetic operations that change a variable (+:=, -:=, *:=, incr, decr) trigger an overflow if the variable would get a value that is less than integer.first or greater than integer.last.
- Division with div and mdiv can overflow because a division by -1 is the same as negating the dividend. E.g.: integer.first div -1 raises OVERFLOW_ERROR.
- Remainder and modulo are defined to raise OVERFLOW_ERROR if the dividend is the most negative integer and the divisor is -1. E.g.: integer.first rem -1 raises OVERFLOW_ERROR. This has been defined as overflow because it can trigger so called undefined behavior of the underlying C code.
- All shift operations (<<, >>, <<:= and >>:=) trigger an overflow if the shift amount is negative or greater equal 64.
- Left shift operations (<< and <<:=) can also trigger an overflow if the shift result would be less than integer.first or greater than integer.last.
- Binomial coefficient (!) triggers an overflow if the result would be less than integer.first or greater than integer.last.
- Computing the absolute value with abs can overflow, if it is called with the most negative integer. E.g.: abs(integer.first) raises OVERFLOW_ERROR.
The interpreter checks always for an integer overflow. By default the compiler generates code to check for an integer overflow. The option -so can be used to suppress the generation of integer overflow checks. If an overflow situation occurs, although overflow checking has been switched off (with -so), the behavior is undefined (see chapter 17.6 Suppressing exception checks).
The separate overflow exception allows easy recognition of overflow situations. All overflow situations, where OVERFLOW_ERROR is raised correspond to C situations, which have undefined behavior. The overflow concept of Seed7 has been designed to allow, that simple C code is generated, if the overflow checks are switched off.
Compiler optimizations (e.g. with -oc2 or -oc3) can reduce the potential of overflow. In an optimized program an expression might be rephrased, such that an overflow is avoided and the correct result is computed instead. Consider the expression:
number + integer.last + integer.first
If number is between succ(integer.first) and 0 the expression can be evaluated and no overflow will occur. For other values of number the exception OVERFLOW_ERROR is raised. When the expression above is optimized it is rephrased to:
pred(number)
This expression only triggers OVERFLOW_ERROR, if number has the value integer.first.
With overflow checks it is guaranteed that an integer overflow always raises OVERFLOW_ERROR. But you cannot rely on OVERFLOW_ERROR being raised if there is an alternate way to return the correct result.
17.3.4 INDEX_ERROR
An INDEX_ERROR occurs if an index is used to access an array, string, bstring or ref_list element beyond the elements that actually exist. E.g. An attempt to get an element of a string, bstring or ref_list with a negative or zero index raises INDEX_ERROR.
- It may be raised from the following array operations:
- [index], [index ..], [.. index], [start .. stop], [index len length], insert, remove.
- It may be raised from the following string operations:
- [index], [index ..], [.. index], [start .. stop], [index len length], [index fixLen length], @:= [index] char, @:= [index] string.
- It may be raised from the following bstring operation:
- [index].
- It may be raised from the following hash operations:
- [.
- It may be raised from the following ref_list operations:
- [index], @:= [index] element.
The interpreter checks always if an index refers to an existing element. By default the compiler generates code to check if indices refer to an existing element. The option -si can be used to suppress the generation of index checks. If a nonexistent element is referred, although index checking has been switched off (with -si), the behavior is undefined (see chapter 17.6 Suppressing exception checks).
17.3.5 RANGE_ERROR
Many functions define a range of valid arguments and raise RANGE_ERROR if this range is violated.
- It may be raised from the following boolean operations:
- conv, parse, boolean, succ, pred, boolean, rand.
- It may be raised from the following integer operations:
- parse, radix, RADIX, sci, rand, integer, bytes, bytes2Int.
- It may be raised from the following bigInteger operations:
- parse, radix, RADIX, sci, rand, integer, bytes, bytes2BigInt, ord, bigInteger, bitLength, modInverse, modPow.
- It may be raised from the following rational operations:
- parse, digits, sci, rational.
- It may be raised from the following bigRational operations:
- parse, digits, sci, bigRational.
- It may be raised from the following float operations:
- parse, digits, sci, float, round, trunc, rand.
- It may be raised from the following complex operations:
- parse, digits, sci, complex.
- It may be raised from the following char operations:
- conv, parse, chr, char, rand, char, trimValue.
- It may be raised from the following string operations:
- mult, pos, rpos.
- It may be raised from the following bitset operations:
- conv, parse, rand, min, max, next, integer, bitset.
- It may be raised from the following array operations:
- times, rand.
- It may be raised from the following bin32 operations:
- radix, RADIX, bytes, float2MbfBits.
- It may be raised from the following bin64 operations:
- radix, RADIX, bin64, bytes, float2MbfBits.
- It may be raised from the following category operations:
- parse, category.
- It may be raised from the following ref_list operations:
- pos.
- It may be raised from the following file operations:
- open, openUtf8, openUtf16le, openUtf16be, openUtf16, openInetSocket, write, writeln, gets, length, truncate, seek, tell, skip.
17.3.6 FILE_ERROR
A FILE_ERROR occurs if an illegal operation with a file is done.
- It may be raised by the following functions:
- fileType, fileTypeSL, fileSize, bigFileSize, getFileMode, setFileMode, getATime, setATime, getCTime, getMTime, setMTime, getOwner, setOwner, getGroup, setGroup, readDir, removeFile, removeTree, moveFile, cloneFile, copyFile, readlink, symlink, hasNext, seek, tell, bigTell, setbuf, write, inetSocketAddress, inetListenerAddress, openInetSocket, openInetListener.
17.3.7 DATABASE_ERROR
The exception DATABASE_ERROR may be raised by database functions. If a DATABASE_ERROR is caught it is possible to get some information about the cause of the error with:
const func string: errMessage (DATABASE_ERROR)
There are messages coming from the database and from the Seed7 database driver. The database driver may have a message like:
Searching for dynamic libraries failed: libclntsh.so
This indicates that the connector library could not be found. In this case the environment variable LD_LIBRARY_PATH could be used to specify the place of the connector library.
17.3.8 GRAPHIC_ERROR
The exception GRAPHIC_ERROR may be raised by graphic drivers. If an underlying graphic library function reports an error a GRAPHIC_ERROR is raised.
17.3.9 ILLEGAL_ACTION
The exception ILLEGAL_ACTION may be raised by the interpreter kernel, if a primitive action does not point to any legal action. This check is only done if the s7 interpreter is compiled with '#define WITH_ACTION_CHECK'. The ILLEGAL_ACTION exception is also raised if the primitive action ACT_ILLEGAL is executed.
17.4 Handlers
To catch an EXCEPTION the following handler construct can be used:
block number := 1 div 0; exception catch NUMERIC_ERROR: number := 1; end block;
It is also possible to catch several exceptions:
block doSomething(someValue); exception catch MEMORY_ERROR: writeln("MEMORY_ERROR"); catch NUMERIC_ERROR: writeln("NUMERIC_ERROR"); end block;
An otherwise handler catches exceptions, that are not caught by the other handlers:
block doSomething(someValue); exception catch RANGE_ERROR: writeln("RANGE_ERROR"); otherwise: writeln("Any other exception"); end block;
17.5 Trace exceptions
The interpreter option -te can be used to trace exceptions and handlers. If an exception occurs the following is written:
*** Exception NUMERIC_ERROR raised at integer.s7i(118) {160000 div fuel_max } at lander.sd7(836) *** Action "INT_DIV" *** The following commands are possible: RETURN Continue * Terminate # Terminate with stack trace / Trigger SIGFPE !n Raise exception with number (e.g.: !1 raises MEMORY_ERROR)
In detail:
- After pressing RETURN the program continues without any change.
- Pressing * and RETURN terminates the program immediately.
- Pressing # and RETURN writes a stack trace and terminates the program. E.g.:
*** Program terminated after exception NUMERIC_ERROR raised with {integer:
*NULL_ENTITY_OBJECT* div fuel_max } Stack: in (val integer: dividend) div (val integer: divisor) at integer.s7i(118) in init_display at lander.sd7(836) in setup at lander.sd7(906) in main at lander.sd7(1536) - Pressing / and RETURN triggers the signal SIGFPE. If the interpreter has been started from a debugger, this triggers the debugger.
17.6 Stack trace
If an exception is not caught the program is terminated and the s7 interpreter writes a stack trace:
*** Uncaught exception NUMERIC_ERROR raised with {integer: <SYMBOLOBJECT> *NULL_ENTITY_OBJECT* div fuel_max } Stack: in (val integer: dividend) div (val integer: divisor) at integer.s7i(118) in init_display at lander.sd7(836) in setup at lander.sd7(906) in main at lander.sd7(1536)
The stack trace shows that a NUMERIC_ERROR was raised by the div operation. This operation is defined in line 118 of integer.s7i. More interesting is that div was called from the function 'init_display' in line 836 of lander.sd7. A NUMERIC_ERROR with div is probably caused by a zero division. A short examination in lander.sd7 shows that an assignment to 'fuel_max' was commented out to show how stack traces work.
A compiled program creates a much shorter crash message:
*** Uncaught exception NUMERIC_ERROR raised at sigutl.c(218)
To get more information there are two possibilities:
- Start the program in the interpreter instead.
- Compile the program with the options -g -e and start it from a debugger.
If s7c is called with the option -g it instructs the C compiler to generate debugging information. This way a debugger like gdb can run the program and provide information. The option -e tells the compiler to generate code which sends a signal, if an uncaught exception occurs. This option allows debuggers to handle uncaught Seed7 exceptions. Note that -e sends the signal SIGFPE. This is done even if the exception is not related to floating point operations.
./s7 s7c -g -e lander gdb ./lander
Then the debugger should be able to run the program and to write a backtrace if a crash occurs:
(gdb) run Starting program: /home/tm/seed7_5/prg/lander Program received signal SIGFPE, Arithmetic exception. 0x000000000041b942 in o_3912_init_display () at lander.sd7:839 839 fuel_gauge := 40 * rocket.fuel div fuel_max; (gdb) bt #0 0x000000000041b942 in o_3912_init_display () at lander.sd7:839 #1 0x000000000041c2e5 in o_3917_setup () at lander.sd7:908 #2 0x0000000000421fe1 in main (argc=1, argv=0x7fffffffdf28) at lander.sd7:1541
Sometimes it is helpful to debug the generated C program instead of the Seed7 source. The option -g-debug_c creates debug information, which refers to the C program generated by the Seed7 compiler:
./s7 s7c -g-debug_c -e lander gdb ./lander
Now the debugger refers to the temporary file tmp_lander.c:
(gdb) run Starting program: /home/tm/seed7_5/prg/lander Program received signal SIGFPE, Arithmetic exception. 0x08068518 in o_2541_init_display () at tmp_lander.c:19727 19727 o_2428_fuel_gauge=((40) * (((structType)(o_2338_rocket))->stru[10].value.intValue/*->o_2336_fuel*/)) / (o_2431_fuel_max); (gdb) bt #0 0x08068518 in o_2541_init_display () at tmp_lander.c:19727 #1 0x08068c21 in o_2546_setup () at tmp_lander.c:19864 #2 0x0806c304 in main (argc=1, argv=0xbffff324) at tmp_lander.c:21188
Some Seed7 exceptions do not send signals. This hinders the debugger to recognize that an uncaught exception occurred. The compiler option -e can help in this situation. It instructs the compiler to generate code which sends a signal if an uncaught exception occurs. This allows the debugger to show a backtrace for uncaught Seed7 exceptions.
17.7 Suppressing exception checks
A Seed7 program can be compiled with the option -sx, to suppress the generation of checks for exceptions. The suppressed checks x are specified with letters from the following list:
- d Suppress the generation of checks for integer division by zero.
- i Suppress the generation of index checks (e.g. string, array).
- o Suppress the generation of integer overflow checks.
- r Suppress the generation of range checks.
If an exception situation occurs, although exception checking has been switched off (with -s), the behavior is undefined. In this case the following things can happen:
- The exception is still raised.
- A different exception is raised.
- The program hangs.
- The program crashes.
- The computation continues with some garbage value. This garbage value can then trigger dangerous things: The X-ray dose computed by your program might be totally wrong. Your program might compute the statics of a bridge wrong.
Undefined behavior is a term used in the language specification of C and in other programming languages. Undefined behavior usually means that the behavior of the program is unpredictable. Normally Seed7 has a well defined behavior in all situations. Even in situations where the language specification of C refers to undefined behavior.
A handler for an exception can only work reliable if the checks for the exception are done. The compiler warns if -s is used and there is a handler for an exception. e.g.:
*** example.sd7(123): Catch of OVERFLOW_ERROR although the checks are suppressed.
Only a program that never raises the specific exception and that does not have a handler for this exception can be considered to be compiled without checks for that exception. Careful program analysis and testing (the exception should never be raised) is necessary to decide about the omission of exception checking.
17.8 Signals
A signal is an asynchronous notification of an event. The event can come from outside such as a request to terminate the program. The event can also come from the program itself such as a memory access violation (segfault). Several signals are handled by the Seed7 run-time library. The interpreter respectively compiler option -ts can be used to influence the behavior, if a signal is sent to a Seed7 program (see below). The following signals are handled by Seed7:
Signal Special handler Behavior without -ts Behavior with -ts SIGABRT raise OVERFLOW_ERROR Raises exception Dialog to decide SIGILL raise OVERFLOW_ERROR Raises exception Dialog to decide SIGTRAP raise OVERFLOW_ERROR Raises exception Dialog to decide SIGINT - Terminate with message Dialog to decide SIGFPE raise NUMERIC_ERROR Raises exception Dialog to decide SIGTERM - Terminate with message Terminate with message SIGSEGV - Terminate program Terminate with message SIGPIPE - Ignored Ignored SIGWINCH Resize console - - SIGALRM Wait for some time - -
Depending on the actual C compiler and operating system the signals SIGABRT, SIGILL or SIGTRAP might be used to raise OVERFLOW_ERROR and the signal SIGFPE might be used to raise NUMERIC_ERROR.
If the interpreter respectively compiler option -ts has been used some signals (see table above) trigger a dialog at the console. E.g.:
*** SIGNAL SIGINT RAISED *** The following commands are possible: RETURN Continue * Terminate / Trigger SIGFPE !n Raise exception with number (e.g.: !1 raises MEMORY_ERROR)
The user can enter a command and activate it with RETURN. If the program was waiting for an input at the console the input can be entered again:
re-enter input>
Triggering SIGFPE is useful if the program runs in a debugger. In this case SIGFPE will activate the debugger prompt. Raising an exception (e.g.: MEMORY_ERROR) can be used to get a stack trace (this works only in the interpreter). A compiled program must be executed with a debugger to get a stack trace.
17.9 Other errors and warnings
- No more memory. Parsing terminated.
- This error message is displayed after the parsing error 1 (Out of heap space). The file name and line number of the analyzer source code where this happens is displayed together with internal heap information.
- System declaration for main missing
- Each program must contain a system declaration that describes which procedure to start as first one.
- Exception %s raised with
- If your trace level specifies exception tracing exceptions and handlers are displayed with this messages and the user must type the ENTER-key to accept.
- Action $%s requires %s not %s
- This error can happen if an action tries to do something with the wrong primitive value. For example adding an integer to a string with INT_ADD. Since the analyze phase checks for the right types this error can only happen if the basic libraries are defined wrong.
- Action $%s with empty value
- This error can happen if an action tries to do something with NULL and NULL is not allowed as value. If parsing works correct this should never happen.
- Action $%s requires variable %s not constant
- This error can happen with actions which assign a value to a constant. Since the analyze phase checks for variable objects this error can only happen if the basic libraries are defined wrong. Principally this error is possible with the following operations: :=, incr, decr, wrd_rd, lin_rd
|
|