From: Rusty Russell Date: Thu, 28 Feb 2008 02:36:47 +0000 (+1100) Subject: Typesafe callback infrastructure. X-Git-Url: https://git.ozlabs.org/?p=ccan;a=commitdiff_plain;h=eded23d444462d115adf0a96b36b7b86a118f7b4 Typesafe callback infrastructure. --- diff --git a/typesafe_cb/_info.c b/typesafe_cb/_info.c new file mode 100644 index 00000000..725b2665 --- /dev/null +++ b/typesafe_cb/_info.c @@ -0,0 +1,59 @@ +#include +#include +#include "config.h" + +/** + * typesafe_cb - macros for safe callbacks. + * + * The basis of the typesafe_cb header is cast_if_type(): a + * conditional cast macro. If an expression exactly matches a given + * type, it is cast to the target type, otherwise it is left alone. + * + * This allows us to create functions which take a small number of + * specific types, rather than being forced to use a void *. In + * particular, it is useful for creating typesafe callbacks as the + * helpers typesafe_cb(), typesafe_cb_preargs() and + * typesafe_cb_postargs() demonstrate. + * + * The standard way of passing arguments to callback functions in C is + * to use a void pointer, which the callback then casts back to the + * expected type. This unfortunately subverts the type checking the + * compiler would perform if it were a direct call. Here's an example: + * + * static void my_callback(void *_obj) + * { + * struct obj *obj = _obj; + * ... + * } + * ... + * register_callback(my_callback, &my_obj); + * + * If we wanted to use the natural type for my_callback (ie. "void + * my_callback(struct obj *obj)"), we could make register_callback() + * take a void * as its first argument, but this would subvert all + * type checking. We really want register_callback() to accept only + * the exactly correct function type to match the argument, or a + * function which takes a void *. + * + * This is where typesafe_cb() comes in: it uses cast_if_type() to + * cast the callback function if it matches the argument type: + * + * void _register_callback(void (*cb)(void *arg), void *arg); + * #define register_callback(cb, arg) \ + * _register_callback(typesafe_cb(void, (cb), (arg)), (arg)) + * + * On compilers which don't support the extensions required + * cast_if_type() and friend become an unconditional cast, so your + * code will compile but you won't get type checking. + */ +int main(int argc, char *argv[]) +{ + if (argc != 2) + return 1; + + if (strcmp(argv[1], "depends") == 0) { + return 0; + } + + return 1; +} diff --git a/typesafe_cb/test/compile_fail-cast_if_type-callback-int.c b/typesafe_cb/test/compile_fail-cast_if_type-callback-int.c new file mode 100644 index 00000000..85006c9b --- /dev/null +++ b/typesafe_cb/test/compile_fail-cast_if_type-callback-int.c @@ -0,0 +1,27 @@ +#include "typesafe_cb/typesafe_cb.h" +#include + +void _callback(void (*fn)(void *arg), void *arg); +void _callback(void (*fn)(void *arg), void *arg) +{ + fn(arg); +} + +/* Callback is set up to warn if arg isn't a pointer (since it won't + * pass cleanly to _callback's second arg. */ +#define callback(fn, arg) \ + _callback(cast_if_type((fn), void (*)(typeof(arg)), void (*)(void *)), \ + arg) + +void my_callback(int something); +void my_callback(int something) +{ +} + +int main(int argc, char *argv[]) +{ +#ifdef FAIL + callback(my_callback, 100); +#endif + return 0; +} diff --git a/typesafe_cb/test/compile_fail-cast_if_type-callback.c b/typesafe_cb/test/compile_fail-cast_if_type-callback.c new file mode 100644 index 00000000..313a53ed --- /dev/null +++ b/typesafe_cb/test/compile_fail-cast_if_type-callback.c @@ -0,0 +1,26 @@ +#include "typesafe_cb/typesafe_cb.h" +#include + +static void _callback(void (*fn)(void *arg), void *arg) +{ + fn(arg); +} + +#define callback(fn, arg) \ + _callback(cast_if_type((fn), void (*)(typeof(arg)), void (*)(void *)), \ + arg) + +static void my_callback(char *p) +{ +} + +int main(int argc, char *argv[]) +{ + callback(my_callback, "hello world"); + +#ifdef FAIL + /* Must be a char * */ + callback(my_callback, my_callback); +#endif + return 0; +} diff --git a/typesafe_cb/test/compile_fail-cast_if_type.c b/typesafe_cb/test/compile_fail-cast_if_type.c new file mode 100644 index 00000000..2ab3fa23 --- /dev/null +++ b/typesafe_cb/test/compile_fail-cast_if_type.c @@ -0,0 +1,22 @@ +#include "typesafe_cb/typesafe_cb.h" + +void _set_some_value(void *val); + +void _set_some_value(void *val) +{ +} + +#define set_some_value(expr) \ + _set_some_value(cast_if_type((expr), unsigned long, void *)) + +int main(int argc, char *argv[]) +{ +#ifdef FAIL + int x = 0; + set_some_value(x); +#else + void *p = 0; + set_some_value(p); +#endif + return 0; +} diff --git a/typesafe_cb/test/compile_fail-typesafe_cb.c b/typesafe_cb/test/compile_fail-typesafe_cb.c new file mode 100644 index 00000000..34050089 --- /dev/null +++ b/typesafe_cb/test/compile_fail-typesafe_cb.c @@ -0,0 +1,25 @@ +#include "typesafe_cb/typesafe_cb.h" +#include + +static void _register_callback(void (*cb)(void *arg), void *arg) +{ +} + +#define register_callback(cb, arg) \ + _register_callback(typesafe_cb(void, (cb), (arg)), (arg)) + +static void my_callback(char *p) +{ +} + +int main(int argc, char *argv[]) +{ +#ifdef FAIL + int *p; +#else + char *p; +#endif + p = NULL; + register_callback(my_callback, p); + return 0; +} diff --git a/typesafe_cb/test/compile_fail-typesafe_cb_postargs.c b/typesafe_cb/test/compile_fail-typesafe_cb_postargs.c new file mode 100644 index 00000000..e6ec8260 --- /dev/null +++ b/typesafe_cb/test/compile_fail-typesafe_cb_postargs.c @@ -0,0 +1,24 @@ +#include "typesafe_cb/typesafe_cb.h" +#include + +static void _register_callback(void (*cb)(void *arg, int x), void *arg) +{ +} +#define register_callback(cb, arg) \ + _register_callback(typesafe_cb_postargs(void, (cb), (arg), int), (arg)) + +static void my_callback(char *p, int x) +{ +} + +int main(int argc, char *argv[]) +{ +#ifdef FAIL + int *p; +#else + char *p; +#endif + p = NULL; + register_callback(my_callback, p); + return 0; +} diff --git a/typesafe_cb/test/compile_fail-typesafe_cb_preargs.c b/typesafe_cb/test/compile_fail-typesafe_cb_preargs.c new file mode 100644 index 00000000..ce1ada2f --- /dev/null +++ b/typesafe_cb/test/compile_fail-typesafe_cb_preargs.c @@ -0,0 +1,25 @@ +#include "typesafe_cb/typesafe_cb.h" +#include + +static void _register_callback(void (*cb)(int x, void *arg), void *arg) +{ +} + +#define register_callback(cb, arg) \ + _register_callback(typesafe_cb_preargs(void, (cb), (arg), int), (arg)) + +static void my_callback(int x, char *p) +{ +} + +int main(int argc, char *argv[]) +{ +#ifdef FAIL + int *p; +#else + char *p; +#endif + p = NULL; + register_callback(my_callback, p); + return 0; +} diff --git a/typesafe_cb/test/run.c b/typesafe_cb/test/run.c new file mode 100644 index 00000000..06753532 --- /dev/null +++ b/typesafe_cb/test/run.c @@ -0,0 +1,148 @@ +#include "typesafe_cb/typesafe_cb.h" +#include +#include "tap.h" + +static char dummy = 0; + +/* The example usage. */ +static void _set_some_value(void *val) +{ + ok1(val == &dummy); +} + +#define set_some_value(expr) \ + _set_some_value(cast_if_type((expr), unsigned long, void *)) + +static void _callback_onearg(void (*fn)(void *arg), void *arg) +{ + fn(arg); +} + +static void _callback_preargs(void (*fn)(int a, int b, void *arg), void *arg) +{ + fn(1, 2, arg); +} + +static void _callback_postargs(void (*fn)(void *arg, int a, int b), void *arg) +{ + fn(arg, 1, 2); +} + +#define callback_onearg(cb, arg) \ + _callback_onearg(typesafe_cb(void, (cb), (arg)), (arg)) + +#define callback_preargs(cb, arg) \ + _callback_preargs(typesafe_cb_preargs(void, (cb), (arg), int, int), (arg)) + +#define callback_postargs(cb, arg) \ + _callback_postargs(typesafe_cb_postargs(void, (cb), (arg), int, int), (arg)) + +static void my_callback_onearg(char *p) +{ + ok1(strcmp(p, "hello world") == 0); +} + +static void my_callback_onearg_const(const char *p) +{ + ok1(strcmp(p, "hello world") == 0); +} + +static void my_callback_onearg_volatile(volatile char *p) +{ + ok1(strcmp((char *)p, "hello world") == 0); +} + +static void my_callback_preargs(int a, int b, char *p) +{ + ok1(a == 1); + ok1(b == 2); + ok1(strcmp(p, "hello world") == 0); +} + +static void my_callback_preargs_const(int a, int b, const char *p) +{ + ok1(a == 1); + ok1(b == 2); + ok1(strcmp(p, "hello world") == 0); +} + +static void my_callback_preargs_volatile(int a, int b, volatile char *p) +{ + ok1(a == 1); + ok1(b == 2); + ok1(strcmp((char *)p, "hello world") == 0); +} + +static void my_callback_postargs(char *p, int a, int b) +{ + ok1(a == 1); + ok1(b == 2); + ok1(strcmp(p, "hello world") == 0); +} + +static void my_callback_postargs_const(const char *p, int a, int b) +{ + ok1(a == 1); + ok1(b == 2); + ok1(strcmp(p, "hello world") == 0); +} + +static void my_callback_postargs_volatile(volatile char *p, int a, int b) +{ + ok1(a == 1); + ok1(b == 2); + ok1(strcmp((char *)p, "hello world") == 0); +} + +/* This is simply a compile test; we promised cast_if_type can be in a + * static initializer. */ +struct callback_onearg +{ + void (*fn)(void *arg); + void *arg; +}; + +struct callback_onearg cb_onearg += { typesafe_cb(void, my_callback_onearg, "hello world"), "hello world" }; + +struct callback_preargs +{ + void (*fn)(int a, int b, void *arg); + void *arg; +}; + +struct callback_preargs cb_preargs += { typesafe_cb_preargs(void, my_callback_preargs, "hi", int, int), "hi" }; + +struct callback_postargs +{ + void (*fn)(void *arg, int a, int b); + void *arg; +}; + +struct callback_postargs cb_postargs += { typesafe_cb_postargs(void, my_callback_postargs, "hi", int, int), "hi" }; + +int main(int argc, char *argv[]) +{ + void *p = &dummy; + unsigned long l = (unsigned long)p; + + plan_tests(2 + 3 + 9 + 9); + set_some_value(p); + set_some_value(l); + + callback_onearg(my_callback_onearg, "hello world"); + callback_onearg(my_callback_onearg_const, "hello world"); + callback_onearg(my_callback_onearg_volatile, "hello world"); + + callback_preargs(my_callback_preargs, "hello world"); + callback_preargs(my_callback_preargs_const, "hello world"); + callback_preargs(my_callback_preargs_volatile, "hello world"); + + callback_postargs(my_callback_postargs, "hello world"); + callback_postargs(my_callback_postargs_const, "hello world"); + callback_postargs(my_callback_postargs_volatile, "hello world"); + + return exit_status(); +} diff --git a/typesafe_cb/typesafe_cb.h b/typesafe_cb/typesafe_cb.h new file mode 100644 index 00000000..cc4ea8ad --- /dev/null +++ b/typesafe_cb/typesafe_cb.h @@ -0,0 +1,120 @@ +#ifndef CCAN_CAST_IF_TYPE_H +#define CCAN_CAST_IF_TYPE_H +#include "config.h" + +#if HAVE_TYPEOF && HAVE_BUILTIN_CHOOSE_EXPR && HAVE_BUILTIN_TYPES_COMPATIBLE_P +/** + * cast_if_type - only cast an expression if it is of a given type + * @expr: the expression to cast + * @oktype: the type we allow + * @desttype: the type to cast to + * + * This macro is used to create functions which allow multiple types. + * The result of this macro is used somewhere that a @desttype type is + * expected: if @expr was of type @oktype, it will be cast to + * @desttype type. As a result, if @expr is any type other than + * @oktype or @desttype, a compiler warning will be issued. + * + * This macro can be used in static initializers. + * + * This is merely useful for warnings: if the compiler does not + * support the primitives required for cast_if_type(), it becomes an + * unconditional cast, and the @oktype argument is not used. In + * particular, this means that @oktype can be a type which uses + * the "typeof": it will not be evaluated if typeof is not supported. + * + * Example: + * // We can take either an unsigned long or a void *. + * void _set_some_value(void *val); + * #define set_some_value(expr) \ + * _set_some_value(cast_if_type((expr), unsigned long, void *)) + */ +#define cast_if_type(expr, oktype, desttype) \ +__builtin_choose_expr(__builtin_types_compatible_p(typeof(1?(expr):0), oktype), \ + (desttype)(expr), (expr)) +#else +#define cast_if_type(expr, oktype, desttype) ((desttype)(expr)) +#endif + +/** + * typesafe_cb - cast a callback function if it matches the arg + * @rettype: the return type of the callback function + * @fn: the callback function to cast + * @arg: the (pointer) argument to hand to the callback function. + * + * If a callback function takes a single argument, this macro does + * appropriate casts to a function which takes a single void * argument if the + * callback provided matches the @arg (or a const or volatile version). + * + * It is assumed that @arg is of pointer type: usually @arg is passed + * or assigned to a void * elsewhere anyway. + * + * Example: + * void _register_callback(void (*fn)(void *arg), void *arg); + * #define register_callback(fn, arg) \ + * _register_callback(typesafe_cb(void, (fn), (arg)), (arg)) + */ +#define typesafe_cb(rettype, fn, arg) \ + cast_if_type(cast_if_type(cast_if_type((fn), \ + rettype (*)(const typeof(arg)), \ + rettype (*)(void *)), \ + rettype (*)(volatile typeof(arg)), \ + rettype (*)(void *)), \ + rettype (*)(typeof(arg)), \ + rettype (*)(void *)) + +/** + * typesafe_cb_preargs - cast a callback function if it matches the arg + * @rettype: the return type of the callback function + * @fn: the callback function to cast + * @arg: the (pointer) argument to hand to the callback function. + * + * This is a version of typesafe_cb() for callbacks that take other arguments + * before the @arg. + * + * Example: + * void _register_callback(void (*fn)(int, void *arg), void *arg); + * #define register_callback(fn, arg) \ + * _register_callback(typesafe_cb_preargs(void, (fn), (arg), int),\ + * (arg)) + */ +#define typesafe_cb_preargs(rettype, fn, arg, ...) \ + cast_if_type(cast_if_type(cast_if_type((fn), \ + rettype (*)(__VA_ARGS__, \ + const typeof(arg)), \ + rettype (*)(__VA_ARGS__, \ + void *)), \ + rettype (*)(__VA_ARGS__, \ + volatile typeof(arg)), \ + rettype (*)(__VA_ARGS__, void *)), \ + rettype (*)(__VA_ARGS__, typeof(arg)), \ + rettype (*)(__VA_ARGS__, void *)) + +/** + * typesafe_cb_postargs - cast a callback function if it matches the arg + * @rettype: the return type of the callback function + * @fn: the callback function to cast + * @arg: the (pointer) argument to hand to the callback function. + * + * This is a version of typesafe_cb() for callbacks that take other arguments + * after the @arg. + * + * Example: + * void _register_callback(void (*fn)(void *arg, int), void *arg); + * #define register_callback(fn, arg) \ + * _register_callback(typesafe_cb_preargs(void, (fn), (arg), int),\ + * (arg)) + */ +#define typesafe_cb_postargs(rettype, fn, arg, ...) \ + cast_if_type(cast_if_type(cast_if_type((fn), \ + rettype (*)(const typeof(arg), \ + __VA_ARGS__), \ + rettype (*)(void *, \ + __VA_ARGS__)), \ + rettype (*)(volatile typeof(arg), \ + __VA_ARGS__), \ + rettype (*)(void *, __VA_ARGS__)), \ + rettype (*)(typeof(arg), __VA_ARGS__), \ + rettype (*)(void *, __VA_ARGS__)) + +#endif /* CCAN_CAST_IF_TYPE_H */