Skip to content

Commit

Permalink
builtin.c: typecheck builtin cfunctions in function_list
Browse files Browse the repository at this point in the history
In C23 (default C standard used by GCC 15),  jv (*fptr)();  has become
equivalent to  jv (*fptr)(void);  so we can no longer assign builtin
implemenations directly to the fptr member of cfunctions without
generating a compile error.

Since there does not seem to be any straight-forward way to tell
autoconf to force the compiler to use C99 short of explicitly adding
-std=c99 to CFLAGS, it is probably a cleaner solution to just make the
code C23 compatible.

A possible solution could have been to just redeclare  cfunction.fptr
as void*, but then the functions' return type would not have been type
checked (e.g. if you tried to add a {printf, "printf", 2}, where printf
is a function that does not return jv, the compiler wouldn't have
complained.)
We were already not typechecking the arguments of the functions, so e.g.
  {binop_plus, "_plus", 3},  /* instead of {f_plus, "_plus, 3},       */
  {f_setpath, "setpath", 4}, /* instead of {f_setpath, "setpath", 3}, */
compile without errors despite not having the correct prototype.

So I thought of instead improving the situation by redefining
cfunction.fptr as a union of function pointers with the prototypes that
the jq bytecode interpreter can call, and use a macro to add the builtin
functions to function_list using to the arity argument to assign the
implementation function to the appropriate union member.

Now the code won't compile if the wrong arity, or an arity not supported
by the bytecode interpreter (>5 = 1input+4arguments), or a prototype not
jallable by the bytecode interpreter (e.g. binop_plus that doesn't
expect a  jq_state*  argument).

Also, the code now compiles with gcc -std=c23.

Fixes #3206
  • Loading branch information
emanuele6 committed Nov 25, 2024
1 parent 96e8d89 commit 72a2a0c
Show file tree
Hide file tree
Showing 3 changed files with 80 additions and 71 deletions.
131 changes: 67 additions & 64 deletions src/builtin.c
Original file line number Diff line number Diff line change
Expand Up @@ -1790,85 +1790,88 @@ static jv f_have_decnum(jq_state *jq, jv a) {
#endif
}

#define CFUNC(func, name, arity) \
{.fptr = { .a ## arity = func }, name, arity}

#define LIBM_DD(name) \
{f_ ## name, #name, 1},
CFUNC(f_ ## name, #name, 1),
#define LIBM_DD_NO(name) LIBM_DD(name)
#define LIBM_DA(name, type) LIBM_DD(name)
#define LIBM_DA_NO(name, type) LIBM_DD(name)

#define LIBM_DDD(name) \
{f_ ## name, #name, 3},
CFUNC(f_ ## name, #name, 3),
#define LIBM_DDD_NO(name) LIBM_DDD(name)

#define LIBM_DDDD(name) \
{f_ ## name, #name, 4},
CFUNC(f_ ## name, #name, 4),
#define LIBM_DDDD_NO(name) LIBM_DDDD(name)

static const struct cfunction function_list[] = {
#include "libm.h"
{f_negate, "_negate", 1},
#define BINOP(name) {f_ ## name, "_" #name, 3},
CFUNC(f_negate, "_negate", 1),
#define BINOP(name) CFUNC(f_ ## name, "_" #name, 3),
BINOPS
#undef BINOP
{f_dump, "tojson", 1},
{f_json_parse, "fromjson", 1},
{f_tonumber, "tonumber", 1},
{f_tostring, "tostring", 1},
{f_keys, "keys", 1},
{f_keys_unsorted, "keys_unsorted", 1},
{f_startswith, "startswith", 2},
{f_endswith, "endswith", 2},
{f_string_split, "split", 2},
{f_string_explode, "explode", 1},
{f_string_implode, "implode", 1},
{f_string_indexes, "_strindices", 2},
{f_string_trim, "trim", 1},
{f_string_ltrim, "ltrim", 1},
{f_string_rtrim, "rtrim", 1},
{f_setpath, "setpath", 3}, // FIXME typechecking
{f_getpath, "getpath", 2},
{f_delpaths, "delpaths", 2},
{f_has, "has", 2},
{f_contains, "contains", 2},
{f_length, "length", 1},
{f_utf8bytelength, "utf8bytelength", 1},
{f_type, "type", 1},
{f_isinfinite, "isinfinite", 1},
{f_isnan, "isnan", 1},
{f_isnormal, "isnormal", 1},
{f_infinite, "infinite", 1},
{f_nan, "nan", 1},
{f_sort, "sort", 1},
{f_sort_by_impl, "_sort_by_impl", 2},
{f_group_by_impl, "_group_by_impl", 2},
{f_min, "min", 1},
{f_max, "max", 1},
{f_min_by_impl, "_min_by_impl", 2},
{f_max_by_impl, "_max_by_impl", 2},
{f_error, "error", 1},
{f_format, "format", 2},
{f_env, "env", 1},
{f_halt, "halt", 1},
{f_halt_error, "halt_error", 2},
{f_get_search_list, "get_search_list", 1},
{f_get_prog_origin, "get_prog_origin", 1},
{f_get_jq_origin, "get_jq_origin", 1},
{f_match, "_match_impl", 4},
{f_modulemeta, "modulemeta", 1},
{f_input, "input", 1},
{f_debug, "debug", 1},
{f_stderr, "stderr", 1},
{f_strptime, "strptime", 2},
{f_strftime, "strftime", 2},
{f_strflocaltime, "strflocaltime", 2},
{f_mktime, "mktime", 1},
{f_gmtime, "gmtime", 1},
{f_localtime, "localtime", 1},
{f_now, "now", 1},
{f_current_filename, "input_filename", 1},
{f_current_line, "input_line_number", 1},
{f_have_decnum, "have_decnum", 1},
{f_have_decnum, "have_literal_numbers", 1},
CFUNC(f_dump, "tojson", 1),
CFUNC(f_json_parse, "fromjson", 1),
CFUNC(f_tonumber, "tonumber", 1),
CFUNC(f_tostring, "tostring", 1),
CFUNC(f_keys, "keys", 1),
CFUNC(f_keys_unsorted, "keys_unsorted", 1),
CFUNC(f_startswith, "startswith", 2),
CFUNC(f_endswith, "endswith", 2),
CFUNC(f_string_split, "split", 2),
CFUNC(f_string_explode, "explode", 1),
CFUNC(f_string_implode, "implode", 1),
CFUNC(f_string_indexes, "_strindices", 2),
CFUNC(f_string_trim, "trim", 1),
CFUNC(f_string_ltrim, "ltrim", 1),
CFUNC(f_string_rtrim, "rtrim", 1),
CFUNC(f_setpath, "setpath", 3),
CFUNC(f_getpath, "getpath", 2),
CFUNC(f_delpaths, "delpaths", 2),
CFUNC(f_has, "has", 2),
CFUNC(f_contains, "contains", 2),
CFUNC(f_length, "length", 1),
CFUNC(f_utf8bytelength, "utf8bytelength", 1),
CFUNC(f_type, "type", 1),
CFUNC(f_isinfinite, "isinfinite", 1),
CFUNC(f_isnan, "isnan", 1),
CFUNC(f_isnormal, "isnormal", 1),
CFUNC(f_infinite, "infinite", 1),
CFUNC(f_nan, "nan", 1),
CFUNC(f_sort, "sort", 1),
CFUNC(f_sort_by_impl, "_sort_by_impl", 2),
CFUNC(f_group_by_impl, "_group_by_impl", 2),
CFUNC(f_min, "min", 1),
CFUNC(f_max, "max", 1),
CFUNC(f_min_by_impl, "_min_by_impl", 2),
CFUNC(f_max_by_impl, "_max_by_impl", 2),
CFUNC(f_error, "error", 1),
CFUNC(f_format, "format", 2),
CFUNC(f_env, "env", 1),
CFUNC(f_halt, "halt", 1),
CFUNC(f_halt_error, "halt_error", 2),
CFUNC(f_get_search_list, "get_search_list", 1),
CFUNC(f_get_prog_origin, "get_prog_origin", 1),
CFUNC(f_get_jq_origin, "get_jq_origin", 1),
CFUNC(f_match, "_match_impl", 4),
CFUNC(f_modulemeta, "modulemeta", 1),
CFUNC(f_input, "input", 1),
CFUNC(f_debug, "debug", 1),
CFUNC(f_stderr, "stderr", 1),
CFUNC(f_strptime, "strptime", 2),
CFUNC(f_strftime, "strftime", 2),
CFUNC(f_strflocaltime, "strflocaltime", 2),
CFUNC(f_mktime, "mktime", 1),
CFUNC(f_gmtime, "gmtime", 1),
CFUNC(f_localtime, "localtime", 1),
CFUNC(f_now, "now", 1),
CFUNC(f_current_filename, "input_filename", 1),
CFUNC(f_current_line, "input_line_number", 1),
CFUNC(f_have_decnum, "have_decnum", 1),
CFUNC(f_have_decnum, "have_literal_numbers", 1),
};
#undef LIBM_DDDD_NO
#undef LIBM_DDD_NO
Expand Down
10 changes: 8 additions & 2 deletions src/bytecode.h
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
#define BYTECODE_H
#include <stdint.h>

#include "jv.h"
#include "jq.h"

typedef enum {
#define OP(name, imm, in, out) name,
Expand Down Expand Up @@ -46,7 +46,13 @@ const struct opcode_description* opcode_describe(opcode op);

#define MAX_CFUNCTION_ARGS 10
struct cfunction {
jv (*fptr)();
union {
jv (*a1)(jq_state *, jv);
jv (*a2)(jq_state *, jv, jv);
jv (*a3)(jq_state *, jv, jv, jv);
jv (*a4)(jq_state *, jv, jv, jv, jv);
jv (*a5)(jq_state *, jv, jv, jv, jv, jv);
} fptr;
const char* name;
int nargs;
};
Expand Down
10 changes: 5 additions & 5 deletions src/execute.c
Original file line number Diff line number Diff line change
Expand Up @@ -917,11 +917,11 @@ jv jq_next(jq_state *jq) {
}
struct cfunction* function = &frame_current(jq)->bc->globals->cfunctions[*pc++];
switch (function->nargs) {
case 1: top = ((jv (*)(jq_state *, jv))function->fptr)(jq, in[0]); break;
case 2: top = ((jv (*)(jq_state *, jv, jv))function->fptr)(jq, in[0], in[1]); break;
case 3: top = ((jv (*)(jq_state *, jv, jv, jv))function->fptr)(jq, in[0], in[1], in[2]); break;
case 4: top = ((jv (*)(jq_state *, jv, jv, jv, jv))function->fptr)(jq, in[0], in[1], in[2], in[3]); break;
case 5: top = ((jv (*)(jq_state *, jv, jv, jv, jv, jv))function->fptr)(jq, in[0], in[1], in[2], in[3], in[4]); break;
case 1: top = function->fptr.a1(jq, in[0]); break;
case 2: top = function->fptr.a2(jq, in[0], in[1]); break;
case 3: top = function->fptr.a3(jq, in[0], in[1], in[2]); break;
case 4: top = function->fptr.a4(jq, in[0], in[1], in[2], in[3]); break;
case 5: top = function->fptr.a5(jq, in[0], in[1], in[2], in[3], in[4]); break;
// FIXME: a) up to 7 arguments (input + 6), b) should assert
// because the compiler should not generate this error.
default: return jv_invalid_with_msg(jv_string("Function takes too many arguments"));
Expand Down

0 comments on commit 72a2a0c

Please sign in to comment.