Greets,
I've opened an issue and uploaded some patches regarding trapping exceptions
from our C code:
https://issues.apache.org/jira/browse/LUCY-230
Trap Exceptions from C
The Clownfish exception mechanism wraps the host language's exception
mechanism, making it possible to throw host exceptions from C code without
knowledge of the host language. We need to augment this capability
with a way to trap exceptions from C.
This new feature will enable us to clean up a couple places in the
code base,
such as opening an index reader. More importantly, though, it will make it
possible to write tests in C which verify error handling.
It's been a challenge to design generic exception handling for Clownfish given
that host languages have very different exception models, and given that C
does not support nested functions so we must pass pointers to concrete
functions.
For reference, here's Wikipedia's survey of exception handling syntax:
http://en.wikipedia.org/wiki/Exception_handling_syntax
Here's the provisional API for the new feature Err#trap:
/** Run <code>routine</code> within the host's exception handling
* environment, catching and returning any errors that occur.
*
* If an unrecognized host exception is trapped, it will be wrapped in an
* Err so that it can be handled by Clownfish code.
*
* @return an Err, or NULL if no exception occurs.
*/
public inert incremented nullable Err*
trap(cfish_Err_attempt_t routine, void *context);
Here's sample usage, where we use Err#trap to implement a "Safe_Do_Stuff"
method which traps "MyError" exceptions which might be thrown by "Do_Stuff":
struct do_stuff_context {
Foo *foo;
int64_t number;
const char *string;
int64_t result;
};
static void
S_run_do_stuff(void *context) {
struct do_stuff_context *args = (struct do_stuff_context*)context;
args.result = Foo_Do_Stuff(args->foo, args->number, args->string);
}
int64_t
Foo_safe_do_stuff(Foo *self, int64_t number, const char *string) {
struct do_stuff_context context;
context.foo = self;
context.number = number;
context.string = string;
Err *error = Err_trap(S_run_do_stuff, &context);
if (error) {
if (Err_Is_A(error, MYERROR)) {
Err_set_error(error);
return -1;
}
else {
RETHROW(error);
}
}
else {
return args.result;
}
}
Thoughts?
Instead of Err_trap(), we could have gone with Err_try() -- but it would not
have had the same semantics as "try" in other languages. It would have been
just like trap(), only returning a boolean indicating whether an exception
occurred and storing the exception object in Err_error rather than returning
it.
The one thing I'm not settled about in the provisional implementation is the
behavior of the XS helper function Err#run, which is where we extract an
arbitrary C function pointer out of a scalar and run it blindly.
void
run(routine_sv, context_sv)
SV *routine_sv;
SV *context_sv;
PPCODE:
IV routine_iv = SvIV(routine_sv);
IV context_iv = SvIV(context_sv);
cfish_Err_attempt_t routine = INT2PTR(cfish_Err_attempt_t, routine_iv);
void *context = INT2PTR(void*, context_iv);
routine(context);
I'm concerned that there might be an attack vector where a malicious user gets
shellcode into a scalar somewhere (easy), and then gets the first argument to
Err#run to point at it (hard, I think). I haven't yet figured out how to
complete that attack, though. Anybody see a problem?
Marvin Humphrey