typesafe printf hackery

From: Andres Freund <andres(at)anarazel(dot)de>
To: pgsql-hackers(at)postgresql(dot)org
Subject: typesafe printf hackery
Date: 2019-07-30 18:18:45
Message-ID: 20190730181845.jyyk4selyohagwnf@alap3.anarazel.de
Views: Raw Message | Whole Thread | Download mbox | Resend email
Thread:
Lists: pgsql-hackers

Hi,

I've occasionally wished for a typesafe version of pg_printf() and other
varargs functions. The compiler warnings are nice, but also far from
complete.

Here's a somewhat crazy hack/prototype for how printf could get actual
argument types. I'm far from certain it's worth pursuing this
further... Nor the contrary.

Note that this requires removing the parentheses from VA_ARGS_NARGS_'s
result (i.e. (N) -> N). To me those parens don't make much sense, we're
pretty much guaranteed to only ever have a number there.

With the following example e.g.

myprintf("boring fmt", 1, 0.1, (char)'c', (void*)0, "crazy stuff");
myprintf("empty argument fmt");

yield

format string "boring fmt", 5 args
arg number 0 is of type int: 1
arg number 1 is of type double: 0.100000
arg number 2 is of type bool: true
arg number 3 is of type void*: (nil)
arg number 4 is of type char*: crazy stuff
format string "empty argument fmt", 0 args

which'd obviously allow for error checking inside myprintf.

#include "c.h"

// hack pg version out of the way
#undef printf

// FIXME: This doesn't correctly work with zero arguments
#define VA_ARGS_EACH(wrap, ...) \
VA_ARGS_EACH_EXPAND(VA_ARGS_NARGS(__VA_ARGS__)) (wrap, __VA_ARGS__)
#define VA_ARGS_EACH_EXPAND(count) VA_ARGS_EACH_EXPAND_REALLY(VA_ARGS_EACH_INT_, count)
#define VA_ARGS_EACH_EXPAND_REALLY(prefix, count) prefix##count

#define VA_ARGS_EACH_INT_1(wrap, el1) wrap(el1)
#define VA_ARGS_EACH_INT_2(wrap, el1, ...) wrap(el1), VA_ARGS_EACH_INT_1(wrap, __VA_ARGS__)
#define VA_ARGS_EACH_INT_3(wrap, el1, ...) wrap(el1), VA_ARGS_EACH_INT_2(wrap, __VA_ARGS__)
#define VA_ARGS_EACH_INT_4(wrap, el1, ...) wrap(el1), VA_ARGS_EACH_INT_3(wrap, __VA_ARGS__)
#define VA_ARGS_EACH_INT_5(wrap, el1, ...) wrap(el1), VA_ARGS_EACH_INT_4(wrap, __VA_ARGS__)
#define VA_ARGS_EACH_INT_6(wrap, el1, ...) wrap(el1), VA_ARGS_EACH_INT_5(wrap, __VA_ARGS__)
#define VA_ARGS_EACH_INT_7(wrap, el1, ...) wrap(el1), VA_ARGS_EACH_INT_6(wrap, __VA_ARGS__)

typedef enum printf_arg_type
{
PRINTF_ARG_BOOL,
PRINTF_ARG_CHAR,
PRINTF_ARG_INT,
PRINTF_ARG_DOUBLE,
PRINTF_ARG_CHARP,
PRINTF_ARG_VOIDP,
} printf_arg_type;

typedef struct arginfo
{
printf_arg_type tp;
} arginfo;

// hackfix empty argument case
#define myprintf(...) myprintf_wrap(__VA_ARGS__, "dummy")
#define myprintf_wrap(fmt, ... ) \
myprintf_impl(fmt, VA_ARGS_NARGS(__VA_ARGS__) - 1, ((arginfo[]){ VA_ARGS_EACH(blurttype, __VA_ARGS__) }), __VA_ARGS__)

// FIXME: Obviously not enough types
#define blurttype(x) ((arginfo){_Generic(x, char: PRINTF_ARG_BOOL, int: PRINTF_ARG_INT, double: PRINTF_ARG_DOUBLE, char *: PRINTF_ARG_CHARP, void *: PRINTF_ARG_VOIDP)})

static const char*
printf_arg_typename(printf_arg_type tp)
{
switch (tp)
{
case PRINTF_ARG_BOOL:
return "bool";
case PRINTF_ARG_CHAR:
return "char";
case PRINTF_ARG_INT:
return "int";
case PRINTF_ARG_DOUBLE:
return "double";
case PRINTF_ARG_CHARP:
return "char*";
case PRINTF_ARG_VOIDP:
return "void*";
}

return "";
}

static void
myprintf_impl(char *fmt, size_t nargs, arginfo arg[], ...)
{
va_list args;
va_start(args, arg);

printf("format string \"%s\", %zu args\n", fmt, nargs);
for (int argno = 0; argno < nargs; argno++)
{
printf("\targ number %d is of type %s: ",
argno,
printf_arg_typename(arg[argno].tp));

switch (arg[argno].tp)
{
case PRINTF_ARG_BOOL:
printf("%s", ((bool) va_arg(args, int)) ? "true" : "false");
break;
case PRINTF_ARG_CHAR:
printf("%c", (char) va_arg(args, int));
break;
case PRINTF_ARG_INT:
printf("%d", va_arg(args, int));
break;
case PRINTF_ARG_DOUBLE:
printf("%f", va_arg(args, double));
break;
case PRINTF_ARG_CHARP:
printf("%s", va_arg(args, char *));
break;
case PRINTF_ARG_VOIDP:
printf("%p", va_arg(args, void *));
break;
}

printf("\n");
}
}

int main(int argc, char **argv)
{
myprintf("boring fmt", 1, 0.1, (char)'c', (void*)0, "crazy stuff");
myprintf("empty argument fmt");
}

Greetings,

Andres Freund

Browse pgsql-hackers by date

  From Date Subject
Next Message Karl O. Pinc 2019-07-30 18:27:08 Re: Patch to document base64 encoding
Previous Message Tomas Vondra 2019-07-30 18:11:34 Re: ANALYZE: ERROR: tuple already updated by self