-
Notifications
You must be signed in to change notification settings - Fork 18
Error handling
When a function in PawnPlus with observable side effects or a function whose purpose is to return a specific value fails to do so, the plugin employs its own specific error handling mechanism built using standard AMX errors to handle the failure, stopping the execution of the callback that caused the error.
When a statement in Pawn cannot fulfill its contract, an AMX error is raised from within the execution engine. For example, if an array is accessed with an index that points to a cell outside the AMX memory range, the programmer's clear intent was for the statement to succeed, and they expect the statement to succeed.
Errors in general are not easily handled in Pawn, as it lacks features specific to languages with exception support. Based on the need to produce clear and simple code, relying on AMX errors was the only reasonable solution. Returning a bool value signaling success (or a specific value signaling the type of error, like COM's HRESULT
) is a viable option as well (and is used in functions like list_get_safe
), but its intent is not clear at first, and it is also not clear what to do if the function fails.
For example, consider the following code:
new List:l = list_new();
list_set(l, 0, 15);
return l;
In this example, list_set
fails because a newly constructed list is empty, so no index can be assigned. list_set
itself could return a success value, but it would be almost always ignored, so most realistically, the error would go unnoticed. The function can be called with assert
prepended to signalize that the function shouldn't fail, but in that case, when is list_set
expected to fail?
Conversely, list_get
would have to be used in this way in order for the success value to be effective:
new value;
assert list_get(l, 0);
printf("%d", value);
Such code is too wordy when compared with printf("%d", list_get(l, 0));
which clearly has the same intent.
Therefore, the best solution is to expect that all functions never fail, and if a failure occurs, it must be either caused by logic errors in the users's code (making handling the error pointless), or by processes which cannot be predicted or averted, and whose failure cannot be reasonably handled. The functions which are expected to fail do so using a clear mechanism using a return value (and a by-ref parameter if an output is needed).
PawnPlus errors aren't meant to be handled in any way other than making the error report as clear as possible, and making sure the server doesn't end up in a corrupted state when such an error happens. In every case where a PawnPlus error happens, a specific native function was used in a way that isn't permitted, or in a situation where it is not supported. Such situations include:
- Calling a function with an incorrect number of arguments (relevant in dynamic calls).
- Using a function on an object which was deleted, or on an otherwise invalid reference.
- Providing an argument whose value is outside the allowed range (list index out of bounds etc.).
- Using a name of a nonexistent function (in
pawn_register_callback
or similar). - Using a string in an invalid format (
str_format
, orstr_match
). - Calling an operation on an iterator that doesn't support it.
- Obtaining the value of a faulted task.
- An unexpected C++ error in implementation.
Moreover, PawnPlus natives produce errors of two different levels: formal errors and logic errors.
Formal errors occur when the issue is usually present at the call site and can be fixed thereat. Only errors related to incorrect arguments are present in this category (but not necessarily all such errors). An incorrect format string or an incorrect number of arguments is a formal error, because the form of the arguments was incorrectly given, and must be fixed explicitly.
A logic error, on the other hand, is usually caused by a mistake in the script's logic. In this case, the arguments are usually in the correct form, but their values are incorrectly computed, or missing explicit checks, in which case either the incorrect value should be fixed, or the whole call should be avoided.
A native function can fail at any point in its implementation, in which case it performs these steps:
- Construct an error with the specific message, source, and level.
- Look for public
pp_on_error
in the script that caused the errors. If the function is found:-
pp_on_error
is called with the error description provided. - If a non-zero value is returned from the function, the value of
retval
is used in place of the original return value from the native, and no error is raised. The original Pawn code continues without interruption.
-
- Otherwise, a message is printed to the log with the error message and source.
-
AMX_ERR_NATIVE
is raised in the script if the error level is not less than the current level set bypp_error_level
(error_logic
by default).
An unhandled error stops the script, allowing plugins like crashdetect to provide a stack dump and other useful information.
The PawnPlus error system does not interfere with normal code, but allows for easy customisation. You can even disable the error system altogether:
public pp_on_error(const source[], const message[], error_level:level, &retval)
{
printf("[PawnPlus] %s: %s", source, message);
retval = 0;
return true;
}
printf("%d", list_delete(List:0)); // 0
All functions in PawnPlus either return a meaningful value, or 1. If you want to handle errors the standard way, you can set your own error-signaling return code, but note that if you omit any check, an error can easily go unnoticed.
If you want to disable errors for a specific call, you can use a custom state machine:
public pp_on_error(const source[], const message[], error_level:level, &retval) <pp_errors:off>
{
retval = 0;
return true;
}
public pp_on_error(const source[], const message[], error_level:level, &retval) <pp_errors:on>
{
return false;
}
public pp_on_error(const source[], const message[], error_level:level, &retval) <>
{
return false;
}
state pp_errors:off;
printf("%d", list_delete(List:0));
state pp_errors:on;
Another option when you want to observe the failure of a function is to use pawn_try_call_native
. This will however not give you access to the error description, which you must store in pp_on_error
for later retrieval.