Commit eded23d4 authored by Rusty Russell's avatar Rusty Russell

Typesafe callback infrastructure.

parent 9927eebe
#include <stdio.h>
#include <string.h>
#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;
}
#include "typesafe_cb/typesafe_cb.h"
#include <stdlib.h>
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;
}
#include "typesafe_cb/typesafe_cb.h"
#include <stdlib.h>
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;
}
#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;
}
#include "typesafe_cb/typesafe_cb.h"
#include <stdlib.h>
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;
}
#include "typesafe_cb/typesafe_cb.h"
#include <stdlib.h>
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;
}
#include "typesafe_cb/typesafe_cb.h"
#include <stdlib.h>
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;
}
#include "typesafe_cb/typesafe_cb.h"
#include <string.h>
#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();
}
#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 */
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment