|
* PCAN
* Phix
edit SideBar
|
Index | Core Language | Statements | TryCatch Statement | throw
| Definition:
| throw(object e, object user_data={})
| | Description:
| Transfer control immediately to some containing try/catch statement, even if it is several levels lower down the call stack, or terminate the program if no such handler exists.
e: typically an integer or string, but it can also be a complete exception in the re-throw case.
user_data: if user_data is a non-empty sequence and e is a string that contains at least one '%' character, then e:=sprintf(e,user_data) is performed and user_data subsequently treated as {}, otherwise if user_data is not not {} (or 0), e must be an atom.
Between them, e and user_data can contain anything that you might find helpful in the handler(/logfile, etc).
A straightforward throw("string") (or the sprintf shorthand) is recommended and when not handled is reported as "unhandled exception (string)" as a special case, whereas other forms, ie user_data!={} or not string(e) are reported in a slightly less helpful way as just "unhandled exception"[1].
| | Comments:
| The throw routine builds an exception and passes it to an appropriate handler.
The short version is: An exception is a sequence of run-time diagnostic information, with the offending instruction address converted to a human-readable source file and line number. The long version is: The following constants (automatically defined in psym.e/syminit) can be used to examine the contents of an exception:
1 E_CODE (atom) hardware and operating system exceptions usually have bit #800000000 set, user exceptions can be any atom value, even 0.
2 E_ADDR (atom) a machine address indicating where the exception ocurred.
3 E_LINE (integer, optional) the source code line matching E_ADDR in E_RTN.
4 E_RTN (integer, optional) equivalent to routine_id(), an index to the symbol table identifying the routine.
5 E_NAME (string|integer, optional) the human-readable name of E_RTN.
6 E_FILE (string|integer, optional) the source filename containing E_NAME.
7 E_PATH (string|integer, optional) the directory containing E_FILE.
8 E_USER (object, optional) user defined/application specific content in e[8..$].
When user_data is a non-empty sequence and e is a string containing at least one '%' character, e:=sprintf(e,user_data); user_data:={} is performed and then:
if user_data!={} then e must be atom and throw(e,user_data) is equivalent to throw({e,-1,-1,-1,-1,-1,-1,user_data}), modified as below.
else if e is an atom, throw(e) is equivalent to throw({e,-1,-1,-1,-1,-1,-1}) [ie length 7], modifed as below.
else if e is a string, throw(e) is equivalent[2] to throw({0,-1,-1,-1,-1,-1,-1,e}), modified as below.
else e must be a sequence containing at least the first two elements, E_CODE and E_ADDR, type safe as above and any user-defined data in e[8..$],
which is padded with -1 to be at least length 7 and then modified as below.
NB Failure to meet the type-safety requirements outlined above, eg throw({"junk"}), results in a non-catchable fatal error.
if e[E_ADDR] is -1 it is replaced with the return address from the throw() call.
if e[E_RTN] is -1 it is replaced with a routine number from the current call stack.
if e[E_LINE] is -1 and e[E_RTN] is valid, then the (new) value of e[ADDR]-1 is mapped to a line number in e[E_RTN] and stored in e[E_LINE].
if e[E_NAME] is -1 the name is retrieved from the symbol table (the string "-1" for top-level subroutines).
Likewise e[E_FILE] and e[E_PATH] of -1 are replaced with the expected strings.
Traditional fatal runtime errors are mapped as follows (with E_ADDR..E_LINE also pre-populated, and E_NAME..E_PATH -1’d as above):
throw(1,"type check error, %s is %s")
throw(2,"attempt to divide by 0")
...
throw(122,"invalid poke size")
See builtins\VM\pDiagN.e for the full/up-to-date list. In the catch clause, e[E_CODE] will be 1..122 and e[E_USER] will be a (post-sprintf) string.
Obviously 55/"unhandled exception" is not and should not be mapped, and if you get one and then try to write a handler to catch it, you might be slightly disappointed, though you should be able to catch the error actually thrown (which could technically be a fake 55/"unhandled exception") rather than get the (real/from pDiagN.e) error 55 being reported on the throw statement (and your application terminated). Likewise 12/"program aborted" (ie '!' keyed in the trace window) is not automatically re-thrown, and hence cannot be caught.
Sometimes you may need to re-throw an exception, and let some containing catch clause process it - which is as simple and straightforward as it could possibly ever be:
try
...
catch e
if <recognised> then
<handle it here>
else
throw(e)
end if
end try
All details are preserved, which also means that you could make up a completely fake exception, with a fictional routine name, line, source file, etc. Obviously I do not recommend that, a unique (per-application and numeric) error code would be much better.
Compilation may issue "Error: without debug in force" messages, since that option suppresses the tables needed for machine address->source code line number mapping.
There is no absolute guarantee that the contents of an exception will be meaningful: any of e[E_RTN..E_PATH] may be left as -1, e[E_USER] may or may not exist, and length(e) may be greater than 8. The catch clause should be coded accordingly/defensively.
Obviously the application will terminate immediately, just like /0, should throw() be invoked when there is no active handler (try/catch construct) in the current call stack, and likewise in that case the above mapping to throw(1..122) does not occur.
| | Performance:
| Exception handling is designed to minimise the impact on code which does /not/ trigger it, and is intended for very infrequent events.
Should you compare the performance overhead of throw("failure") with return false then unfortunately you are in for a very big shock.
Building detailed run-time diagnostic information and converting a machine address into a human-readable line number, file, and routine name, is simply always going to take considerably longer than a single "mov eax,ebx" instruction!
Should you need to process 10,000 items, but 2 of them trigger an exception, the savings of simplified code (such as not checking for division by zero, whether a file of that name already exists, etc) on 9,998 of them may very well significantly outweigh the cost of the two exceptions that are triggered. It is in that sense, alone, that appropriate use of exceptions can make some programs noticeably faster.
However, the wilful and excessive use of throw() in quite unnecessary situtations will almost inevitably result in a slower program.
That said, any code not being iterated many thousands of times is better off being as clear and intuitive as you can make it.
| | Example 1:
|
try
?9/0
-- throw(2,"attempt to divide by 0") -- equivalent/as triggered via pDiagN.e/diag().
catch e
?{"raw:",e}
if length(e)>=E_USER
and string(e[E_USER])
and string(e[E_FILE])
and integer(e[E_LINE])
and e[E_LINE]!=-1 then
printf(1,"%s (%s line %d)\n",{e[E_USER],e[E_FILE],e[E_LINE]})
else
?{"oops... (test.exw line 13)",e}
end if
end try
Output:
{"raw:",{2,8249772,2,21,"-1","test.exw",`C:\Program Files (x86)\Phix\`,"attempt to divide by 0"}}
attempt to divide by 0 (test.exw line 2)
While the above conditions/printf should give you the basic idea of what may be required, be advised that that using cut-and-paste may cause unforseen problems, since the precise content of an exception can vary quite dramatically. Although those five tests on 'e' cover everything I can think of right now, creative use of throw() is quite likely to find a way to make that catch clause as it stands do the completely wrong thing.
| | Example 2:
|
throw("Parameter n contains %d digits: too big",{nd})
throw("Negative %age")
You can use the built-in sprintf?() functionality of throw() in a natural way, but you can also pass a string without any additional arguments and not worry about escaping any '%' characters.
| | See Also:
| try/catch, crash_file?, crash_message?, crash_routine?, era
| | Technicalia
| Implemented in builtins\VM\pDiagN.e in order to use the existing code for converting a machine address into a source code line number, etc.
Hence any use of throw(), like crash_file?, crash_message?, and crash_routine?, makes the (rarely used) command line option -nodiag completely ineffective.
(Said option is normally only used after a bug in the compiler has been painstakingly pared down to the absolute bare minimum.)
The system stack is automatically rebalanced when a catch clause is executed, which proved necessary when catching any errors in nested c_func/proc and/or call_back routines.
The use of throw() may prevent control returning to the interpreter (as pDiagN.e calls :%NoCleanup) and hence there may be problems trying to add proper testing of try/catch/throw() to "p -test", however test/terror.exw ought to be fine.
You should not need to test e[E_NAME] for "-1" if the try statement is not at top-level, or does not contain any actual failure points itself.
Minor point: throw(x) is mapped to throw(x,0) in pmain.e, and then to throw(x,<unassigned>) in pilx86.e, which causes the default of {} to apply.
Consequently, an explicit throw(x,0) would suffer much the same fate, ie be treated as throw(x,{}).
This occurs because of the way it is implemented, via :%pThrow (in pDiagN.e), rather than as a normal hll routine.
Likewise, routine_id?("throw") yields -1, instead you would have to write a mini-shim.
[1] I will quickly justify that behaviour by saying that if you are going to provide more information in the exceptions you throw, there is an underlying assumption you are going to try and do something useful with them, plus everything that you need for debugging/removing your uncaught exception should still be in the ex.err file, and besides trying to sprint(e[E_CODE..E_USER]) (when it is not a string) into a neat little one-liner is all rather likely to get way too messy and confusing anyway.
[2] For completeness I should say that they, ie throw("string") & throw({0,-1,-1,-1,-1,-1,-1,"string"}), are not precisely equivalent in that their uncaught error messages differ, and that includes plain throw("string") that are re-thrown using the method shown above, rather than (say) [@<small>if length(e)=E_USER and string(e[E_USER]) then </small>throw(e[E_USER])<small>
< comparison with Java | Index | Special Top-Level Statements >
|
|