From: Rusty Russell Date: Tue, 1 Aug 2023 01:43:53 +0000 (+0930) Subject: base64: fix for unsigned chars (e.g. ARM). X-Git-Url: http://git.ozlabs.org/?p=ccan;a=commitdiff_plain;h=HEAD;hp=209a81909942a07cb334d6dcae7626a3ecde141d base64: fix for unsigned chars (e.g. ARM). ``` ccan/ccan/base64/base64.c:34:10: error: result of comparison of constant 255 with expression of type 'int8_t' (aka 'signed char') is always false [-Werror,-Wtautological-constant-out-of-range-compare] if (ret == (char)0xff) { ~~~ ^ ~~~~~~~~~~ ccan/ccan/base64/base64.c:44:57: error: result of comparison of constant 255 with expression of type 'const signed char' is always true [-Werror,-Wtautological-constant-out-of-range-compare] return (maps->decode_map[(const unsigned char)b64char] != (char)0xff); ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ^ ~~~~~~~~~~ ``` Reported-by: Christian Decker Signed-off-by: Rusty Russell --- diff --git a/Makefile b/Makefile index d53e89f4..875e99f2 100644 --- a/Makefile +++ b/Makefile @@ -8,7 +8,7 @@ all:: # Our flags for building WARN_CFLAGS := -Wall -Wstrict-prototypes -Wold-style-definition -Wundef \ - -Wmissing-prototypes -Wmissing-declarations -Wpointer-arith -Wwrite-strings + -Wmissing-prototypes -Wmissing-declarations -Wpointer-arith -Wwrite-strings -Wshadow=local DEP_CFLAGS = -MMD -MP -MF$(@:%=%.d) -MT$@ CCAN_CFLAGS := -g3 -ggdb $(WARN_CFLAGS) -DCCAN_STR_DEBUG=1 -I. $(CFLAGS) CFLAGS_FORCE_C_SOURCE := -x c diff --git a/Makefile-web b/Makefile-web index 29ca254e..ae388c0c 100644 --- a/Makefile-web +++ b/Makefile-web @@ -18,6 +18,7 @@ JUNKPAGES=$(JUNKDIRS:%=$(WEBDIR)/%.html) JUNKBALLS=$(JUNKDIRS:%=$(WEBDIR)/%.tar.bz2) PRETTIFY=$(WEBDIR)/prettify/src/run_prettify.js $(WEBDIR)/prettify/src/prettify.css +VERSION_NUM := $(shell git describe | cut -d- -f2) clean-tree: [ "$(WEBDEV)" ] || ! git status --porcelain | grep . @@ -40,14 +41,17 @@ $(WEBDIR)/junkcode/%.html: $(WEBDIR)/junkcode/%.tar.bz2 cd $(WEBDIR) && tar xfj junkcode/$*.tar.bz2 URLPREFIX=../ $(PHP) web/staticjunkcode.php junkcode/$* $* > $@ +$(WEBDIR)/ccan.tar.bz2: $(WEBDIR)/ccan-$(VERSION_NUM).tar.bz2 + ln -sf ccan-$(VERSION_NUM).tar.bz2 ccan.tar.bz2 + # We want tarball to contain ccan/; we put junkcode in, but don't depend on it. -$(WEBDIR)/ccan.tar.bz2: config.h Makefile $(shell git ls-files ccan tools licenses) +$(WEBDIR)/ccan-$(VERSION_NUM).tar.bz2: config.h Makefile $(shell git ls-files ccan tools licenses) DIR=`pwd` && cd /tmp && ln -sf "$$DIR" ccan && tar cvfj $@ `for f in $^; do echo ccan/$$f; done` ccan/junkcode && rm ccan $(ALL_PAGES): tools/doc_extract web/staticmoduleinfo.php $(WEBDIR)/list.html: web/staticall.php tools/doc_extract $(DIRECT_TARBALLS) $(DEPEND_TARBALLS) $(WEBDIR)/ccan.tar.bz2 $(JUNKBALLS) - $(PHP) web/staticall.php ccan/ junkcode/ $(WEBDIR) $(MODS) > $@ + $(PHP) web/staticall.php ccan/ junkcode/ $(WEBDIR) ccan-$(VERSION_NUM).tar.bz2 $(MODS) > $@ $(WEBDIR)/upload.html: web/staticupload.php $(PHP) web/staticupload.php > $@ @@ -57,7 +61,7 @@ $(WEBDIR)/uploader.php: web/uploader.php.cpp cpp -w -C -P $< | grep . > $@ $(WEBDIR)/index.html: web/staticindex.php - $(PHP) web/staticindex.php > $@ + $(PHP) web/staticindex.php ccan-$(VERSION_NUM).tar.bz2 > $@ $(WEBDIR)/example-config.h: config.h cp $< $@ diff --git a/ccan/antithread/alloc/alloc.c b/ccan/antithread/alloc/alloc.c index 1b36aa47..33201ab9 100644 --- a/ccan/antithread/alloc/alloc.c +++ b/ccan/antithread/alloc/alloc.c @@ -728,8 +728,6 @@ void *alloc_get(void *pool, unsigned long poolsize, bs = &head->bs[bucket]; if (!bs->page_list) { - struct page_header *ph; - if (large_page_bucket(bucket, sp_bits)) bs->page_list = get_large_page(head, poolsize, sp_bits); diff --git a/ccan/antithread/alloc/tiny.c b/ccan/antithread/alloc/tiny.c index ffd17c65..d84974ee 100644 --- a/ccan/antithread/alloc/tiny.c +++ b/ccan/antithread/alloc/tiny.c @@ -353,7 +353,8 @@ bool tiny_alloc_check(void *pool, unsigned long poolsize) unsigned long arrsize = free_array_size(poolsize); unsigned char *arr = pool; unsigned long len, off, hdrlen; - unsigned long i, freearr[arrsize], num_freearr = 0; + /* Don't have sanitizer complain here if arrsize is 0! */ + unsigned long i, freearr[arrsize ? arrsize : 1], num_freearr = 0; bool free; if (poolsize < MIN_BLOCK_SIZE) diff --git a/ccan/base64/base64.c b/ccan/base64/base64.c index a216f478..c28e0da2 100644 --- a/ccan/base64/base64.c +++ b/ccan/base64/base64.c @@ -31,7 +31,7 @@ static int8_t sixbit_from_b64(const base64_maps_t *maps, int8_t ret; ret = maps->decode_map[(unsigned char)b64letter]; - if (ret == (char)0xff) { + if (ret == '\xff') { errno = EDOM; return -1; } @@ -41,7 +41,7 @@ static int8_t sixbit_from_b64(const base64_maps_t *maps, bool base64_char_in_alphabet(const base64_maps_t *maps, const char b64char) { - return (maps->decode_map[(const unsigned char)b64char] != (char)0xff); + return (maps->decode_map[(const unsigned char)b64char] != '\xff'); } void base64_init_maps(base64_maps_t *dest, const char src[64]) @@ -118,7 +118,7 @@ size_t base64_decoded_length(size_t srclen) return ((srclen+3)/4*3); } -int base64_decode_quartet_using_maps(const base64_maps_t *maps, char dest[3], +ssize_t base64_decode_quartet_using_maps(const base64_maps_t *maps, char dest[3], const char src[4]) { signed char a; @@ -143,7 +143,7 @@ int base64_decode_quartet_using_maps(const base64_maps_t *maps, char dest[3], } -int base64_decode_tail_using_maps(const base64_maps_t *maps, char dest[3], +ssize_t base64_decode_tail_using_maps(const base64_maps_t *maps, char dest[3], const char * src, const size_t srclen) { char longsrc[4]; @@ -178,7 +178,7 @@ ssize_t base64_decode_using_maps(const base64_maps_t *maps, { ssize_t dest_offset = 0; ssize_t i; - size_t more; + ssize_t more; if (destlen < base64_decoded_length(srclen)) { errno = EOVERFLOW; diff --git a/ccan/base64/base64.h b/ccan/base64/base64.h index 405dc63f..a899af4a 100644 --- a/ccan/base64/base64.h +++ b/ccan/base64/base64.h @@ -103,8 +103,8 @@ ssize_t base64_decode_using_maps(const base64_maps_t *maps, * @return Number of decoded bytes set in dest. -1 on error (and errno set) * @note sets errno = EDOM if src contains invalid characters */ -int base64_decode_quartet_using_maps(const base64_maps_t *maps, - char dest[3], const char src[4]); +ssize_t base64_decode_quartet_using_maps(const base64_maps_t *maps, + char dest[3], const char src[4]); /** * base64_decode_tail_using_maps - decode the final bytes of a base64 string using a specific alphabet @@ -116,8 +116,8 @@ int base64_decode_quartet_using_maps(const base64_maps_t *maps, * @note sets errno = EDOM if src contains invalid characters * @note sets errno = EINVAL if src is an invalid base64 tail */ -int base64_decode_tail_using_maps(const base64_maps_t *maps, char *dest, - const char *src, size_t srclen); +ssize_t base64_decode_tail_using_maps(const base64_maps_t *maps, char dest[3], + const char *src, size_t srclen); /* the rfc4648 functions: */ @@ -212,7 +212,7 @@ ssize_t base64_decode(char *dest, size_t destlen, * @note sets errno = EDOM if src contains invalid characters */ static inline -int base64_decode_quartet(char dest[3], const char src[4]) +ssize_t base64_decode_quartet(char dest[3], const char src[4]) { return base64_decode_quartet_using_maps(&base64_maps_rfc4648, dest, src); diff --git a/ccan/bitops/test/run.c b/ccan/bitops/test/run.c index 5dba932d..6bb3acf5 100644 --- a/ccan/bitops/test/run.c +++ b/ccan/bitops/test/run.c @@ -10,7 +10,7 @@ int main(void) plan_tests(68 + 6 * (31 + 63)); for (i = 0; i < 32; i++) - ok1(bitops_ffs32(1 << i) == i+1); + ok1(bitops_ffs32(1U << i) == i+1); ok1(bitops_ffs32(0) == 0); for (i = 0; i < 64; i++) ok1(bitops_ffs64((uint64_t)1 << i) == i+1); @@ -25,19 +25,19 @@ int main(void) ok1(bitops_ffs64(0) == 0); for (i = 0; i < 32; i++) - ok1(bitops_clz32(1 << i) == 31 - i); + ok1(bitops_clz32(1U << i) == 31 - i); for (i = 0; i < 64; i++) ok1(bitops_clz64((uint64_t)1 << i) == 63 - i); /* Lower bits don't effect results */ for (i = 0; i < 32; i++) - ok1(bitops_clz32((1 << i) + (1 << i)-1) == 31 - i); + ok1(bitops_clz32((1U << i) + (1U << i)-1) == 31 - i); for (i = 0; i < 64; i++) ok1(bitops_clz64(((uint64_t)1 << i) + ((uint64_t)1 << i)-1) == 63 - i); for (i = 0; i < 32; i++) - ok1(bitops_ctz32(1 << i) == i); + ok1(bitops_ctz32(1U << i) == i); for (i = 0; i < 64; i++) ok1(bitops_ctz64((uint64_t)1 << i) == i); diff --git a/ccan/bytestring/bytestring.h b/ccan/bytestring/bytestring.h index bc99e795..a0689db1 100644 --- a/ccan/bytestring/bytestring.h +++ b/ccan/bytestring/bytestring.h @@ -203,8 +203,13 @@ static inline const char *bytestring_rindex(struct bytestring haystack, static inline struct bytestring bytestring_bytestring(struct bytestring haystack, struct bytestring needle) { - const char *p = memmem(haystack.ptr, haystack.len, - needle.ptr, needle.len); + const char *p; + + /* Allow needle.ptr == NULL, without memmem sanitizer complaining */ + if (needle.len == 0) + return bytestring(haystack.ptr, 0); + + p = memmem(haystack.ptr, haystack.len, needle.ptr, needle.len); if (p) return bytestring(p, needle.len); else diff --git a/ccan/closefrom/LICENSE b/ccan/closefrom/LICENSE new file mode 120000 index 00000000..b7951dab --- /dev/null +++ b/ccan/closefrom/LICENSE @@ -0,0 +1 @@ +../../licenses/CC0 \ No newline at end of file diff --git a/ccan/closefrom/_info b/ccan/closefrom/_info new file mode 100644 index 00000000..4dc1d7fa --- /dev/null +++ b/ccan/closefrom/_info @@ -0,0 +1,69 @@ +#include "config.h" +#include +#include + +/** + * closefrom - close all fds starting from specified fd. + * + * This code is an example of what to do in a child process to + * ensure that none of the (possibly sensitive) file descriptors + * in the parent remain in the child process. + * + * License: CC0 (Public domain) + * Author: ZmnSCPxj jxPCSnmZ + * + * Example: + * #include + * #include + * #include + * #include + * #include + * #include + * #include + * #include + * + * int main(int argc, char **argv) + * { + * pid_t child; + * + * // If being emulated, then we might end up + * // looping over a large _SC_OPEN_MAX + * // (Some systems have it as INT_MAX!) + * // If so, closefrom_limit will lower this limit + * // to a value you specify, or if given 0 will + * // limit to 4096. + * // Call this as early as possible. + * closefrom_limit(0); + * + * // If we limited, we can query this so we can + * // print it in debug logs or something. + * if (closefrom_may_be_slow()) + * printf("we limited ourselves to 4096 fds.\n"); + * + * child = fork(); + * if (child < 0) + * err(1, "Forking"); + * if (child == 0) { + * closefrom(STDERR_FILENO + 1); + * // Insert your *whatever* code here. + * _exit(0); + * } + * + * waitpid(child, NULL, 0); + * + * return 0; + * } + * + */ +int main(int argc, char *argv[]) +{ + /* Expect exactly one argument */ + if (argc != 2) + return 1; + + if (strcmp(argv[1], "depends") == 0) { + return 0; + } + + return 1; +} diff --git a/ccan/closefrom/closefrom.c b/ccan/closefrom/closefrom.c new file mode 100644 index 00000000..177b05ee --- /dev/null +++ b/ccan/closefrom/closefrom.c @@ -0,0 +1,225 @@ +/* CC0 license (public domain) - see LICENSE file for details */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* See also: + * https://stackoverflow.com/a/918469 + * + * The implementation below is not exhaustive of all the suggested above. + */ + +#if !HAVE_CLOSEFROM + +/* IBM AIX. + * https://www.ibm.com/docs/en/aix/7.2?topic=f-fcntl-dup-dup2-subroutine + */ +#if HAVE_F_CLOSEM + +#include + +void closefrom(int fromfd) +{ + (void) fcntl(fromfd, F_CLOSEM, 0); +} + +bool closefrom_may_be_slow(void) +{ + return false; +} + +#else /* !HAVE_F_CLOSEM */ + +#if HAVE_NR_CLOSE_RANGE +#include +#endif + +#define PROC_PID_FD_LEN \ + ( 6 /* /proc/ */ \ + + 20 /* 64-bit $PID */ \ + + 3 /* /fd */ \ + + 1 /* NUL */ \ + ) + +static bool can_get_maxfd(void) +{ +#if HAVE_F_MAXFD + int res = fcntl(0, F_MAXFD); + if (res < 0) + return false; + else + return true; +#else + return false; +#endif +} + +/* Linux >= 5.9 */ +static bool can_close_range(void) +{ +#if HAVE_NR_CLOSE_RANGE + int res = syscall(__NR_close_range, INT_MAX, INT_MAX, 0); + if (res < 0) + return false; + return true; +#else + return false; +#endif +} + +/* On Linux, Solaris, AIX, Cygwin, and NetBSD. */ +static bool can_open_proc_pid_fd(void) +{ + char dnam[PROC_PID_FD_LEN]; + DIR *dir; + + sprintf(dnam, "/proc/%ld/fd", (long) getpid()); + dir = opendir(dnam); + if (!dir) + return false; + closedir(dir); + return true; +} + +/* On FreeBSD and MacOS. */ +static bool can_open_dev_fd(void) +{ + DIR *dir; + dir = opendir("/dev/fd"); + if (!dir) + return false; + closedir(dir); + return true; +} + +bool closefrom_may_be_slow(void) +{ + if (can_get_maxfd()) + return false; + else if (can_close_range()) + return false; + else if (can_open_proc_pid_fd()) + return false; + else if (can_open_dev_fd()) + return false; + else + return true; +} + +/* It is possible that we run out of available file descriptors. + * However, if we are going to close anyway, we could just try + * closing file descriptors until we reach maxfd. + */ +static +DIR *try_opendir(const char *dnam, int *fromfd, int maxfd) +{ + DIR *dir; + + do { + dir = opendir(dnam); + if (!dir && (errno == ENFILE || errno == EMFILE)) { + if (*fromfd < maxfd) + close((*fromfd)++); + else + break; + } + } while (!dir && (errno == ENFILE || errno == EMFILE)); + + return dir; +} + +void closefrom(int fromfd) +{ + int saved_errno = errno; + + int res; + int maxfd; + + char dnam[PROC_PID_FD_LEN]; + DIR *dir; + struct dirent *entry; + + (void) res; + + if (fromfd < 0) + goto quit; + +#if HAVE_NR_CLOSE_RANGE + res = syscall(__NR_close_range, fromfd, INT_MAX, 0); + if (res == 0) + goto quit; +#endif + + maxfd = sysconf(_SC_OPEN_MAX); + + sprintf(dnam, "/proc/%ld/fd", (long) getpid()); + dir = try_opendir(dnam, &fromfd, maxfd); + if (!dir) + dir = try_opendir("/dev/fd", &fromfd, maxfd); + + if (dir) { + while ((entry = readdir(dir))) { + long fd; + char *endp; + + fd = strtol(entry->d_name, &endp, 10); + if (entry->d_name != endp && *endp == '\0' && + fd >= 0 && fd < INT_MAX && fd >= fromfd && + fd != dirfd(dir) ) + close(fd); + } + closedir(dir); + goto quit; + } + +#if HAVE_F_MAXFD + res = fcntl(0, F_MAXFD); + if (res >= 0) + maxfd = res + 1; +#endif + + /* Fallback. */ + for (; fromfd < maxfd; ++fromfd) + close(fromfd); + +quit: + errno = saved_errno; +} + +#endif /* !HAVE_F_CLOSEM */ + +void closefrom_limit(unsigned int arg_limit) +{ + rlim_t limit = (rlim_t) arg_limit; + + struct rlimit nofile; + + if (!closefrom_may_be_slow()) + return; + + if (limit == 0) + limit = 4096; + + getrlimit(RLIMIT_NOFILE, &nofile); + + /* Respect the max limit. + * If we are not running as root then we cannot raise + * it, but we *can* lower the max limit. + */ + if (nofile.rlim_max != RLIM_INFINITY && limit > nofile.rlim_max) + limit = nofile.rlim_max; + + nofile.rlim_cur = limit; + nofile.rlim_max = limit; + + setrlimit(RLIMIT_NOFILE, &nofile); +} + +#endif /* !HAVE_CLOSEFROM */ diff --git a/ccan/closefrom/closefrom.h b/ccan/closefrom/closefrom.h new file mode 100644 index 00000000..790a5ba3 --- /dev/null +++ b/ccan/closefrom/closefrom.h @@ -0,0 +1,81 @@ +/* CC0 license (public domain) - see LICENSE file for details */ +#ifndef CCAN_CLOSEFROM_H +#define CCAN_CLOSEFROM_H +#include "config.h" +#include + +#if HAVE_CLOSEFROM +/* BSD. */ +#include +/* Solaris. */ +#include + +static inline +bool closefrom_may_be_slow(void) +{ + return 0; +} + +static inline +void closefrom_limit(unsigned int limit) +{ +} + +#else /* !HAVE_CLOSEFROM */ + +/** + * closefrom - Close all open file descriptors, starting + * at fromfd onwards. + * @fromfd: the first fd to close; it and all higher file descriptors + * will be closed. + * + * This is not multithread-safe: other threads in the same process + * may or may not open new file descriptors in parallel to this call. + * However, the expected use-case is that this will be called in a + * child process just after fork(), meaning the child process is still + * single-threaded. + */ +void closefrom_(int fromfd); +/* In case the standard library has it, but declared in some + * *other* header we do not know of yet, we use closefrom_ in + * the actual name the linker sees. + */ +#define closefrom closefrom_ + +/** + * closefrom_may_be_slow - check if the closefrom() function could + * potentially take a long time. + * + * The return value is true if closefrom() is emulated by + * looping from fromfd to sysconf(_SC_OPEN_MAX), which can be + * very large (possibly even INT_MAX on some systems). + * If so, you might want to use setrlimit to limit _SC_OPEN_MAX. + * If this returns false, then closefrom is efficient and you do not + * need to limit the number of file descriptors. + * + * You can use closefrom_limit to perform the limiting based on + * closefrom_may_be_slow. + * This API is exposed in case you want to output to debug logs or + * something similar. + */ +bool closefrom_may_be_slow(void); + +/** + * closefrom_limit - If closefrom_may_be_slow(), lower the limit on + * the number of file descriptors we keep open, to prevent closefrom + * from being *too* slow. + * @limit: 0 to use a reasonable default of 4096, or non-zero for the + * limit you prefer. + * + * This function does nothing if closefrom_may_be_slow() return false. + * + * This function only *lowers* the limit from the hard limit set by + * root before running this program. + * If the limit is higher than the hard limit, then the hard limit is + * respected. + */ +void closefrom_limit(unsigned int limit); + +#endif /* !HAVE_CLOSEFROM */ + +#endif /* CCAN_CLOSEFROM_H */ diff --git a/ccan/closefrom/test/run.c b/ccan/closefrom/test/run.c new file mode 100644 index 00000000..10e40158 --- /dev/null +++ b/ccan/closefrom/test/run.c @@ -0,0 +1,192 @@ +#include +/* Include the C files directly. */ +#include +#include +#include +#include +#include +#include + +/* Open a pipe, do closefrom, check pipe no longer works. */ +static +int pipe_close(void) +{ + int fds[2]; + ssize_t wres; + + char buf = '\0'; + + if (pipe(fds) < 0) + return 0; + + /* Writing to the write end should succeed, the + * pipe is working. */ + do { + wres = write(fds[1], &buf, 1); + } while ((wres < 0) && (errno == EINTR)); + if (wres < 0) + return 0; + + closefrom(STDERR_FILENO + 1); + + /* Writing to the write end should fail because + * everything should be closed. */ + do { + wres = write(fds[1], &buf, 1); + } while ((wres < 0) && (errno == EINTR)); + + return (wres < 0) && (errno == EBADF); +} + +/* Open a pipe, fork, do closefrom in child, read pipe from parent, + * parent should see EOF. + */ +static +int fork_close(void) +{ + int fds[2]; + pid_t child; + + char buf; + ssize_t rres; + + if (pipe(fds) < 0) + return 0; + + child = fork(); + if (child < 0) + return 0; + + if (child == 0) { + /* Child. */ + closefrom(STDERR_FILENO + 1); + _exit(0); + } else { + /* Parent. */ + + /* Close write end of pipe. */ + close(fds[1]); + + do { + rres = read(fds[0], &buf, 1); + } while ((rres < 0) && (errno == EINTR)); + + /* Should have seen EOF. */ + if (rres != 0) + return 0; + + /* Clean up. */ + waitpid(child, NULL, 0); + closefrom(STDERR_FILENO + 1); + } + + return 1; +} +/* Open a pipe, fork, in child set the write end to fd #3, + * in parent set the read end to fd #3, send a byte from + * child to parent, check. + */ +static +int fork_communicate(void) +{ + int fds[2]; + pid_t child; + + char wbuf = 42; + char rbuf; + ssize_t rres; + ssize_t wres; + + int status; + + if (pipe(fds) < 0) + return 0; + + child = fork(); + if (child < 0) + return 0; + + if (child == 0) { + /* Child. */ + + /* Move write end to fd #3. */ + if (fds[1] != 3) { + if (dup2(fds[1], 3) < 0) + _exit(127); + close(fds[1]); + fds[1] = 3; + } + + closefrom(4); + + do { + wres = write(fds[1], &wbuf, 1); + } while ((wres < 0) && (errno == EINTR)); + if (wres < 0) + _exit(127); + + _exit(0); + } else { + /* Parent. */ + + /* Move read end to fd #3. */ + if (fds[0] != 3) { + if (dup2(fds[0], 3) < 0) + return 0; + close(fds[0]); + fds[0] = 3; + } + + closefrom(4); + + /* Wait for child to finish. */ + waitpid(child, &status, 0); + if (!WIFEXITED(status)) + return 0; + if (WEXITSTATUS(status) != 0) + return 0; + + /* Read 1 byte. */ + do { + rres = read(fds[0], &rbuf, 1); + } while ((rres < 0) && (errno == EINTR)); + if (rres < 0) + return 0; + if (rres != 1) + return 0; + /* Should get same byte as what was sent. */ + if (rbuf != wbuf) + return 0; + + /* Next attempt to read should EOF. */ + do { + rres = read(fds[0], &rbuf, 1); + } while ((rres < 0) && (errno == EINTR)); + if (rres < 0) + return 0; + /* Should EOF. */ + if (rres != 0) + return 0; + + } + + /* Clean up. */ + close(fds[0]); + return 1; +} + +int main(void) +{ + /* Limit closefrom. */ + closefrom_limit(0); + + /* This is how many tests you plan to run */ + plan_tests(3); + + ok1(pipe_close()); + ok1(fork_close()); + ok1(fork_communicate()); + + /* This exits depending on whether all tests passed */ + return exit_status(); +} diff --git a/ccan/compiler/compiler.h b/ccan/compiler/compiler.h index 1bbb3b8b..562b29ec 100644 --- a/ccan/compiler/compiler.h +++ b/ccan/compiler/compiler.h @@ -271,6 +271,19 @@ #define NON_NULL_ARGS(...) #endif +#if HAVE_ATTRIBUTE_RETURNS_NONNULL +/** + * RETURNS_NONNULL - specify that this function cannot return NULL. + * + * Mainly an optimization opportunity, but can also suppress warnings. + * + * Example: + * RETURNS_NONNULL char *my_copy(char *buf); + */ +#define RETURNS_NONNULL __attribute__((__returns_nonnull__)) +#else +#define RETURNS_NONNULL +#endif #if HAVE_ATTRIBUTE_SENTINEL /** diff --git a/ccan/crc32c/LICENSE b/ccan/crc32c/LICENSE new file mode 120000 index 00000000..2354d129 --- /dev/null +++ b/ccan/crc32c/LICENSE @@ -0,0 +1 @@ +../../licenses/BSD-MIT \ No newline at end of file diff --git a/ccan/crc32c/crc32c.c b/ccan/crc32c/crc32c.c index 9eaec79b..0203a22b 100644 --- a/ccan/crc32c/crc32c.c +++ b/ccan/crc32c/crc32c.c @@ -72,7 +72,8 @@ static inline uint32_t gf2_matrix_times(uint32_t *mat, uint32_t vec) { /* Multiply a matrix by itself over GF(2). Both mat and square must have 32 rows. */ static inline void gf2_matrix_square(uint32_t *square, uint32_t *mat) { - for (unsigned n = 0; n < 32; n++) + unsigned n; + for (n = 0; n < 32; n++) square[n] = gf2_matrix_times(mat, mat[n]); } @@ -87,7 +88,8 @@ static void crc32c_zeros_op(uint32_t *even, size_t len) { /* put operator for one zero bit in odd */ odd[0] = POLY; /* CRC-32C polynomial */ uint32_t row = 1; - for (unsigned n = 1; n < 32; n++) { + unsigned n; + for (n = 1; n < 32; n++) { odd[n] = row; row <<= 1; } @@ -111,7 +113,7 @@ static void crc32c_zeros_op(uint32_t *even, size_t len) { } while (len); /* answer ended up in odd -- copy to even */ - for (unsigned n = 0; n < 32; n++) + for (n = 0; n < 32; n++) even[n] = odd[n]; } @@ -121,7 +123,8 @@ static void crc32c_zeros(uint32_t zeros[][256], size_t len) { uint32_t op[32]; crc32c_zeros_op(op, len); - for (unsigned n = 0; n < 256; n++) { + unsigned n; + for (n = 0; n < 256; n++) { zeros[0][n] = gf2_matrix_times(op, n); zeros[1][n] = gf2_matrix_times(op, n << 8); zeros[2][n] = gf2_matrix_times(op, n << 16); @@ -265,7 +268,8 @@ uint32_t crc32c(uint32_t crc, void const *buf, size_t len) { static bool crc32c_once_little; static uint32_t crc32c_table_little[8][256]; static void crc32c_init_sw_little(void) { - for (unsigned n = 0; n < 256; n++) { + unsigned n; + for (n = 0; n < 256; n++) { uint32_t crc = n; crc = crc & 1 ? (crc >> 1) ^ POLY : crc >> 1; crc = crc & 1 ? (crc >> 1) ^ POLY : crc >> 1; @@ -277,9 +281,10 @@ static void crc32c_init_sw_little(void) { crc = crc & 1 ? (crc >> 1) ^ POLY : crc >> 1; crc32c_table_little[0][n] = crc; } - for (unsigned n = 0; n < 256; n++) { + for (n = 0; n < 256; n++) { uint32_t crc = crc32c_table_little[0][n]; - for (unsigned k = 1; k < 8; k++) { + unsigned k; + for (k = 1; k < 8; k++) { crc = crc32c_table_little[0][crc & 0xff] ^ (crc >> 8); crc32c_table_little[k][n] = crc; } @@ -340,7 +345,8 @@ static bool crc32c_once_big; static uint32_t crc32c_table_big_byte[256]; static uint64_t crc32c_table_big[8][256]; static void crc32c_init_sw_big(void) { - for (unsigned n = 0; n < 256; n++) { + unsigned n; + for (n = 0; n < 256; n++) { uint32_t crc = n; crc = crc & 1 ? (crc >> 1) ^ POLY : crc >> 1; crc = crc & 1 ? (crc >> 1) ^ POLY : crc >> 1; @@ -352,10 +358,11 @@ static void crc32c_init_sw_big(void) { crc = crc & 1 ? (crc >> 1) ^ POLY : crc >> 1; crc32c_table_big_byte[n] = crc; } - for (unsigned n = 0; n < 256; n++) { + for (n = 0; n < 256; n++) { uint32_t crc = crc32c_table_big_byte[n]; crc32c_table_big[0][n] = swap(crc); - for (unsigned k = 1; k < 8; k++) { + unsigned k; + for (k = 1; k < 8; k++) { crc = crc32c_table_big_byte[crc & 0xff] ^ (crc >> 8); crc32c_table_big[k][n] = swap(crc); } diff --git a/ccan/crypto/hmac_sha256/hmac_sha256.c b/ccan/crypto/hmac_sha256/hmac_sha256.c index 0392afe5..2238f9dc 100644 --- a/ccan/crypto/hmac_sha256/hmac_sha256.c +++ b/ccan/crypto/hmac_sha256/hmac_sha256.c @@ -35,7 +35,8 @@ void hmac_sha256_init(struct hmac_sha256_ctx *ctx, * (e.g., if K is of length 20 bytes and B=64, then K will be * appended with 44 zero bytes 0x00) */ - memcpy(k_ipad, k, ksize); + if (ksize != 0) + memcpy(k_ipad, k, ksize); memset((char *)k_ipad + ksize, 0, HMAC_SHA256_BLOCKSIZE - ksize); /* diff --git a/ccan/crypto/xtea/LICENSE b/ccan/crypto/xtea/LICENSE new file mode 120000 index 00000000..08d5d486 --- /dev/null +++ b/ccan/crypto/xtea/LICENSE @@ -0,0 +1 @@ +../../../licenses/CC0 \ No newline at end of file diff --git a/ccan/crypto/xtea/xtea.c b/ccan/crypto/xtea/xtea.c index 28435d6a..43ce5573 100644 --- a/ccan/crypto/xtea/xtea.c +++ b/ccan/crypto/xtea/xtea.c @@ -11,8 +11,9 @@ uint64_t xtea_encipher(const struct xtea_secret *secret, uint64_t v) { const uint32_t delta=0x9E3779B9; uint32_t v0=(v>>32), v1=v, sum=0; + int i; - for (int i=0; i < NUM_DOUBLE_ROUNDS; i++) { + for (i=0; i < NUM_DOUBLE_ROUNDS; i++) { v0 += (((v1 << 4) ^ (v1 >> 5)) + v1) ^ (sum + secret->u.u32[sum & 3]); sum += delta; v1 += (((v0 << 4) ^ (v0 >> 5)) + v0) ^ (sum + secret->u.u32[(sum>>11) & 3]); @@ -24,8 +25,9 @@ uint64_t xtea_decipher(const struct xtea_secret *secret, uint64_t e) { const uint32_t delta=0x9E3779B9; uint32_t v0=(e>>32), v1=e, sum=delta*NUM_DOUBLE_ROUNDS; + int i; - for (int i=0; i < NUM_DOUBLE_ROUNDS; i++) { + for (i=0; i < NUM_DOUBLE_ROUNDS; i++) { v1 -= (((v0 << 4) ^ (v0 >> 5)) + v0) ^ (sum + secret->u.u32[(sum>>11) & 3]); sum -= delta; v0 -= (((v1 << 4) ^ (v1 >> 5)) + v1) ^ (sum + secret->u.u32[sum & 3]); diff --git a/ccan/daemonize/daemonize.c b/ccan/daemonize/daemonize.c index bd32ecbb..ff580724 100644 --- a/ccan/daemonize/daemonize.c +++ b/ccan/daemonize/daemonize.c @@ -6,7 +6,7 @@ #include #include -/* This code is based on Stevens Advanced Programming in the UNIX +/* This code is based on Stevens' Advanced Programming in the UNIX * Environment. */ bool daemonize(void) { diff --git a/ccan/darray/darray.h b/ccan/darray/darray.h index 58470fde..0b98fdac 100644 --- a/ccan/darray/darray.h +++ b/ccan/darray/darray.h @@ -183,15 +183,21 @@ typedef darray(unsigned long) darray_ulong; #define darray_append_items(arr, items, count) do { \ size_t count_ = (count), oldSize_ = (arr).size; \ - darray_resize(arr, oldSize_ + count_); \ - memcpy((arr).item + oldSize_, items, count_ * sizeof(*(arr).item)); \ + /* Don't memcpy NULL! */ \ + if (count_) { \ + darray_resize(arr, oldSize_ + count_); \ + memcpy((arr).item + oldSize_, items, count_ * sizeof(*(arr).item)); \ + } \ } while(0) #define darray_prepend_items(arr, items, count) do { \ size_t count_ = (count), oldSize_ = (arr).size; \ darray_resize(arr, count_ + oldSize_); \ - memmove((arr).item + count_, (arr).item, oldSize_ * sizeof(*(arr).item)); \ - memcpy((arr).item, items, count_ * sizeof(*(arr).item)); \ + /* Don't memcpy NULL! */ \ + if (count_) { \ + memmove((arr).item + count_, (arr).item, oldSize_ * sizeof(*(arr).item)); \ + memcpy((arr).item, items, count_ * sizeof(*(arr).item)); \ + } \ } while(0) #define darray_append_items_nullterminate(arr, items, count) do { \ diff --git a/ccan/edit_distance/edit_distance_dl.c b/ccan/edit_distance/edit_distance_dl.c index 695f50d3..f3f0cb16 100644 --- a/ccan/edit_distance/edit_distance_dl.c +++ b/ccan/edit_distance/edit_distance_dl.c @@ -41,10 +41,11 @@ ed_dist edit_distance_dl(const ed_elem *src, ed_size slen, ed_dist *delcost = malloc(ED_TMAT_SIZE(slen + 1) * sizeof(ed_dist)); ed_dist *delcostitr = delcost; ed_dist *delcostprevitr = delcost; + ed_size i2, i1; *delcostitr++ = 0; - for (ed_size i2 = 1; i2 <= slen; ++i2) { + for (i2 = 1; i2 <= slen; ++i2) { ed_dist costi2 = ED_DEL_COST(src[i2 - 1]); - for (ed_size i1 = 0; i1 < i2; ++i1) { + for (i1 = 0; i1 < i2; ++i1) { *delcostitr++ = *delcostprevitr++ + costi2; } *delcostitr++ = 0; @@ -61,10 +62,11 @@ ed_dist edit_distance_dl(const ed_elem *src, ed_size slen, ed_dist *inscost = malloc(ED_TMAT_SIZE(tlen + 1) * sizeof(ed_dist)); ed_dist *inscostitr = inscost; ed_dist *inscostprevitr = inscost; + ed_size j2, j1; *inscostitr++ = 0; - for (ed_size j2 = 1; j2 <= tlen; ++j2) { + for (j2 = 1; j2 <= tlen; ++j2) { ed_dist costj2 = ED_INS_COST(tgt[j2 - 1]); - for (ed_size j1 = 0; j1 < j2; ++j1) { + for (j1 = 0; j1 < j2; ++j1) { *inscostitr++ = *inscostprevitr++ + costj2; } *inscostitr++ = 0; @@ -73,7 +75,8 @@ ed_dist edit_distance_dl(const ed_elem *src, ed_size slen, #endif /* Initialize first row with maximal cost */ - for (ed_size i = 0; i < slen + 2; ++i) { + ed_size i, j; + for (i = 0; i < slen + 2; ++i) { dist[i] = maxdist; } @@ -83,11 +86,11 @@ ed_dist edit_distance_dl(const ed_elem *src, ed_size slen, /* Initialize row with cost to delete src[0..i-1] */ dist[-1] = maxdist; dist[0] = 0; - for (ed_size i = 1; i <= slen; ++i) { + for (i = 1; i <= slen; ++i) { dist[i] = dist[i - 1] + ED_DEL_COST(src[i - 1]); } - for (ed_size j = 1; j <= tlen; ++j) { + for (j = 1; j <= tlen; ++j) { /* Largest y < i such that src[y] = tgt[j] */ ed_size lastsrc = 0; ed_dist *prevdist = dist; @@ -101,7 +104,8 @@ ed_dist edit_distance_dl(const ed_elem *src, ed_size slen, * Loop invariant: lasttgt[ED_HASH_ELEM(c)] holds the largest * x < j such that tgt[x-1] = c or 0 if no such x exists. */ - for (ed_size i = 1; i <= slen; ++i) { + ed_size i; + for (i = 1; i <= slen; ++i) { ed_size i1 = lastsrc; ed_size j1 = lasttgt[ED_HASH_ELEM(src[i - 1])]; diff --git a/ccan/edit_distance/edit_distance_lcs.c b/ccan/edit_distance/edit_distance_lcs.c index 4bc867e6..190009c7 100644 --- a/ccan/edit_distance/edit_distance_lcs.c +++ b/ccan/edit_distance/edit_distance_lcs.c @@ -13,6 +13,8 @@ ed_dist edit_distance_lcs(const ed_elem *src, ed_size slen, const ed_elem *tgt, ed_size tlen) { + ed_size i, j; + /* Optimization: Avoid malloc when row of distance matrix can fit on * the stack. */ @@ -24,11 +26,11 @@ ed_dist edit_distance_lcs(const ed_elem *src, ed_size slen, /* Initialize row with cost to delete src[0..i-1] */ dist[0] = 0; - for (ed_size i = 1; i <= slen; ++i) { + for (i = 1; i <= slen; ++i) { dist[i] = dist[i - 1] + ED_DEL_COST(src[i - 1]); } - for (ed_size j = 1; j <= tlen; ++j) { + for (j = 1; j <= tlen; ++j) { /* Value for dist[j-1][i-1] (one row up, one col left). */ ed_dist diagdist = dist[0]; dist[0] = dist[0] + ED_INS_COST(tgt[j - 1]); @@ -36,7 +38,7 @@ ed_dist edit_distance_lcs(const ed_elem *src, ed_size slen, /* Loop invariant: dist[i] is the edit distance between first j * elements of tgt and first i elements of src. */ - for (ed_size i = 1; i <= slen; ++i) { + for (i = 1; i <= slen; ++i) { ed_dist nextdiagdist = dist[i]; if (ED_ELEM_EQUAL(src[i - 1], tgt[j - 1])) { diff --git a/ccan/edit_distance/edit_distance_lev.c b/ccan/edit_distance/edit_distance_lev.c index fefa9da8..352dbd78 100644 --- a/ccan/edit_distance/edit_distance_lev.c +++ b/ccan/edit_distance/edit_distance_lev.c @@ -13,6 +13,8 @@ ed_dist edit_distance_lev(const ed_elem *src, ed_size slen, const ed_elem *tgt, ed_size tlen) { + ed_size i, j; + /* Optimization: Avoid malloc when row of distance matrix can fit on * the stack. */ @@ -24,11 +26,11 @@ ed_dist edit_distance_lev(const ed_elem *src, ed_size slen, /* Initialize row with cost to delete src[0..i-1] */ dist[0] = 0; - for (ed_size i = 1; i <= slen; ++i) { + for (i = 1; i <= slen; ++i) { dist[i] = dist[i - 1] + ED_DEL_COST(src[i - 1]); } - for (ed_size j = 1; j <= tlen; ++j) { + for (j = 1; j <= tlen; ++j) { /* Value for dist[j-1][i-1] (one row up, one col left). */ ed_dist diagdist = dist[0]; dist[0] = dist[0] + ED_INS_COST(tgt[j - 1]); @@ -36,7 +38,7 @@ ed_dist edit_distance_lev(const ed_elem *src, ed_size slen, /* Loop invariant: dist[i] is the edit distance between first j * elements of tgt and first i elements of src. */ - for (ed_size i = 1; i <= slen; ++i) { + for (i = 1; i <= slen; ++i) { ed_dist nextdiagdist = dist[i]; if (ED_ELEM_EQUAL(src[i - 1], tgt[j - 1])) { diff --git a/ccan/edit_distance/edit_distance_rdl.c b/ccan/edit_distance/edit_distance_rdl.c index 4f098cbe..90414484 100644 --- a/ccan/edit_distance/edit_distance_rdl.c +++ b/ccan/edit_distance/edit_distance_rdl.c @@ -13,6 +13,8 @@ ed_dist edit_distance_rdl(const ed_elem *src, ed_size slen, const ed_elem *tgt, ed_size tlen) { + ed_size i, j; + /* Optimization: Avoid malloc when required rows of distance matrix can * fit on the stack. */ @@ -32,11 +34,11 @@ ed_dist edit_distance_rdl(const ed_elem *src, ed_size slen, /* Initialize row with cost to delete src[0..i-1] */ dist[0] = 0; - for (ed_size i = 1; i <= slen; ++i) { + for (i = 1; i <= slen; ++i) { dist[i] = dist[i - 1] + ED_DEL_COST(src[i - 1]); } - for (ed_size j = 1; j <= tlen; ++j) { + for (j = 1; j <= tlen; ++j) { /* Value for dist[j-2][i-1] (two rows up, one col left). */ /* Note: dist[0] is not initialized when j == 1, var unused. */ ed_dist diagdist1 = prevdist[0]; @@ -51,7 +53,7 @@ ed_dist edit_distance_rdl(const ed_elem *src, ed_size slen, /* Loop invariant: dist[i] is the edit distance between first j * elements of tgt and first i elements of src. */ - for (ed_size i = 1; i <= slen; ++i) { + for (i = 1; i <= slen; ++i) { ed_dist nextdiagdist = dist[i]; if (ED_ELEM_EQUAL(src[i - 1], tgt[j - 1])) { diff --git a/ccan/failtest/failtest.c b/ccan/failtest/failtest.c index c61ce442..205ded25 100644 --- a/ccan/failtest/failtest.c +++ b/ccan/failtest/failtest.c @@ -179,7 +179,8 @@ static struct failtest_call *add_history_(enum failtest_call_type type, call->line = line; call->cleanup = NULL; call->backtrace = get_backtrace(&call->backtrace_num); - memcpy(&call->u, elem, elem_size); + if (elem_size != 0) + memcpy(&call->u, elem, elem_size); tlist_add_tail(&history, call, list); return call; } @@ -1202,9 +1203,8 @@ static void cleanup_pipe(struct pipe_call *call, bool restore) int failtest_pipe(int pipefd[2], const char *file, unsigned line) { struct failtest_call *p; - struct pipe_call call; - p = add_history(FAILTEST_PIPE, true, file, line, &call); + p = add_history_(FAILTEST_PIPE, true, file, line, NULL, 0); if (should_fail(p)) { p->u.open.ret = -1; /* FIXME: Play with error codes? */ diff --git a/ccan/graphql/LICENSE b/ccan/graphql/LICENSE new file mode 120000 index 00000000..2354d129 --- /dev/null +++ b/ccan/graphql/LICENSE @@ -0,0 +1 @@ +../../licenses/BSD-MIT \ No newline at end of file diff --git a/ccan/graphql/_info b/ccan/graphql/_info new file mode 100644 index 00000000..29202414 --- /dev/null +++ b/ccan/graphql/_info @@ -0,0 +1,66 @@ +#include "config.h" +#include +#include + +/** + * graphql - Routines to lex and parse GraphQL. + * + * This code contains routines to lex and parse GraphQL code. + * This code was written per the spec at: + * https://spec.graphql.org/draft/ + * ...dated Fri, May 21, 2021 at the time of writing. + * Copyright (c) 2021 WhiteCloudFarm.org + * + * + * Example: + * + * int main(int argc, char *argv[]) { + * + * const char *input_string = "{ fieldName }"; + * struct list_head *output_tokens; + * struct graphql_executable_document *output_document; + * + * const char *errmsg = graphql_lexparse( + * NULL, // tal context + * input_string, + * &output_tokens, // variable to receive tokens + * &output_document); // variable to receive AST + * + * if (errmsg) { + * struct graphql_token *last_token; + * last_token = list_tail(output_tokens, struct graphql_token, node); + * printf("Line %d, col %d: %s", + * last_token->source_line, + * last_token->source_column + last_token->source_len, + * errmsg); + * } else { + * // Normally you would check every indirection in the resulting AST for null + * // pointers, but for simplicity of example: + * printf("A field from the parsed string: %s\n", + * output_document->first_def->op_def->sel_set-> + * first->field->name->token_string); + * } + * + * output_tokens = tal_free(output_tokens); + * } + * + * License: BSD-MIT + */ +int main(int argc, char *argv[]) +{ + /* Expect exactly one argument */ + if (argc != 2) + return 1; + + if (strcmp(argv[1], "depends") == 0) { + printf("ccan/list\n"); + printf("ccan/str\n"); + printf("ccan/tal\n"); + printf("ccan/tal/str\n"); + printf("ccan/utf8\n"); + return 0; + } + + return 1; +} + diff --git a/ccan/graphql/graphql.c b/ccan/graphql/graphql.c new file mode 100644 index 00000000..640a76bf --- /dev/null +++ b/ccan/graphql/graphql.c @@ -0,0 +1,968 @@ +/* MIT (BSD) license - see LICENSE file for details */ +#include "graphql.h" + +#include "ccan/tal/str/str.h" +#include "ccan/utf8/utf8.h" + + +/* GraphQL character classes + * + * These definitions are meant to reflect the GraphQL specification as + * literally as possible. + */ +#define SOURCE_CHAR(c) ((c) == '\t' || (c) == '\n' || (c) == '\r' || ((c) >= 32 && (c) <= 65535)) +#define WHITE_SPACE(c) ((c) == '\t' || (c) == ' ') +#define LINE_TERMINATOR(c) ((c) == '\n' || (c) == '\r') +#define COMMENT(c) ((c) == '#') +#define COMMENT_CHAR(c) (SOURCE_CHAR(c) && !LINE_TERMINATOR(c)) +#define STRING_CHAR(c) (SOURCE_CHAR(c) && !LINE_TERMINATOR(c) && (c)!='"' && (c)!='\\') +#define BLOCK_STRING_CHAR(c) (SOURCE_CHAR(c)) +#define COMMA(c) ((c) == ',') +#define EOF_CHAR(c) ((c) == 0 || (c) == 4) +#define PUNCTUATOR(c) (strchr("!$&().:=@[]{|}", c)) +#define HEX_DIGIT(c) (DIGIT(c) || ((c) >= 'a' && (c) <= 'f') || ((c) >= 'A' && (c) <= 'F')) +#define DIGIT(c) ((c) >= '0' && (c) <= '9') +#define NAME_START(c) (((c) >= 'a' && (c) <= 'z') || ((c) >= 'A' && (c) <= 'Z') || (c) == '_') +#define NAME_CONTINUE(c) (NAME_START(c) || DIGIT(c)) + +// Helper for copying an overlapping string, since strcpy() is not safe for that +#define cpystr(d,s) { char *cpystr_p; char *cpystr_q; for(cpystr_p = (s), cpystr_q = (d); *cpystr_p;) *cpystr_q++ = *cpystr_p++; *cpystr_q++ = *cpystr_p++; } + +/* Parser shorthands + * + * These shorthands are motivated by the parser functions, so they can be + * written in a format that corresponds closely to the specification. + */ +#define RET static void * +#define PARAMS struct list_head *tokens, struct list_head *used, const char **err +#define ARGS tokens, used, err +#define INIT(type) \ + struct graphql_token *rollback_top = list_top(tokens, struct graphql_token, node); \ + struct graphql_##type *obj = talz(tokens, struct graphql_##type); \ + (void)rollback_top; /* avoids unused variable warning */ \ + +#define EXIT \ + goto exit_label; /* avoids unused label warning */ \ + exit_label: \ + if (*err) obj = tal_free(obj); \ + return obj; \ + +#define CONSUME_ONE list_add(used, &list_pop(tokens, struct graphql_token, node)->node); +#define RESTORE_ONE list_add(tokens, &list_pop(used, struct graphql_token, node)->node); +#define ROLLBACK(args) while (list_top(tokens, struct graphql_token, node) != rollback_top) { RESTORE_ONE; } +#define OR if (!*err) goto exit_label; *err = NULL; +#define REQ if (*err) { ROLLBACK(args); goto exit_label; } +#define OPT *err = NULL; +#define WHILE_OPT while(!*err); *err = NULL; +#define LOOKAHEAD(args, tok) struct graphql_token *tok = list_top(tokens, struct graphql_token, node); +#define MSG(msg) if (*err) *err = msg; + + +/* The following parser functions are written in a way that corresponds to the + * grammar defined in the GraphQL specification. The code is not intended to + * look like normal C code; it's designed for parsing clarity rather than C + * style. Think of it as something generated rather than something to read. + * For that reason, the functions follow special rules: + * + * - The declaration is standardized with RET and PARAMS + * - The "err" argument is assumed to be NULL upon entrance + * - The "err" argument is set on failure + * - If the function fails to parse, then "tokens" shall be as it was upon entrance + * - INIT and EXIT macros are used + * - Macros such as REQ and OPT facilitate readability and conciseness + */ + +/* The following functions construct the "leaves" of the abstract syntax tree. */ + +RET parse_keyword(PARAMS, const char *keyword, const char *errmsg) { + struct graphql_token *tok = list_top(tokens, struct graphql_token, node); + if (!tok || tok->token_type != 'a') { + *err = errmsg; return NULL; + } + if (!streq(tok->token_string, keyword)) { + *err = errmsg; return NULL; + } + CONSUME_ONE; + return tok; +} + +// Note: a static buffer is used here. +RET parse_punct(PARAMS, int punct) { + static char punctbuf[16]; + struct graphql_token *tok = list_top(tokens, struct graphql_token, node); + if (!tok || tok->token_type != punct) { + if (punct == PUNCT_SPREAD) + sprintf(punctbuf, "expected: '...'"); + else + sprintf(punctbuf, "expected: '%c'", punct); + *err = punctbuf; return NULL; + } + CONSUME_ONE; + return tok; +} + +RET parse_name(PARAMS) { + struct graphql_token *tok = list_top(tokens, struct graphql_token, node); + if (!tok || tok->token_type != 'a') { + *err = "name expected"; return NULL; + } + CONSUME_ONE; + return tok; +} + +RET parse_int(PARAMS) { + struct graphql_token *tok = list_top(tokens, struct graphql_token, node); + if (!tok || tok->token_type != 'i') { + *err = "integer expected"; return NULL; + } + CONSUME_ONE; + return tok; +} + +RET parse_float(PARAMS) { + struct graphql_token *tok = list_top(tokens, struct graphql_token, node); + if (!tok || tok->token_type != 'f') { + *err = "float expected"; return NULL; + } + CONSUME_ONE; + return tok; +} + +RET parse_string(PARAMS) { + struct graphql_token *tok = list_top(tokens, struct graphql_token, node); + if (!tok || tok->token_type != 's') { + *err = "string expected"; return NULL; + } + CONSUME_ONE; + return tok; +} + +// The following functions create the branches of the AST. + +/* +RET parse_non_null_type_2(PARAMS) { + INIT(non_null_type); + parse_list_type(ARGS); REQ; + parse_punct(ARGS, '!'); REQ; + EXIT; +} + +RET parse_non_null_type_1(PARAMS) { + INIT(non_null_type); + parse_named_type(ARGS); REQ; + parse_punct(ARGS, '!'); REQ; + EXIT; +} + +RET parse_non_null_type(PARAMS) { + INIT(non_null_type); + parse_non_null_type_1(ARGS); OR + parse_non_null_type_2(ARGS); + EXIT; +} + +RET parse_list_type(PARAMS) { + INIT(list_type); + parse_punct(ARGS, '['); REQ + parse_type(ARGS); REQ + parse_punct(ARGS, ']'); REQ + EXIT; +} +*/ + +RET parse_named_type(PARAMS) { + INIT(named_type); + obj->name = parse_name(ARGS); + EXIT; +} + +RET parse_type(PARAMS) { + INIT(type); + obj->named = parse_named_type(ARGS); +/* + OR + obj->list = parse_list_type(ARGS); OR + obj->non_null = parse_non_null_type(ARGS); +*/ + EXIT; +} + +RET parse_variable(PARAMS) { + INIT(variable); + parse_punct(ARGS, '$'); REQ + obj->name = parse_name(ARGS); REQ + EXIT; +} + +RET parse_value(PARAMS); + +RET parse_list_value(PARAMS) { + INIT(list_value); + parse_punct(ARGS, '['); REQ + parse_punct(ARGS, ']'); + while (*err) { + *err = NULL; + parse_value(ARGS); MSG("expected: value or ']'"); REQ + parse_punct(ARGS, ']'); + } + EXIT; +} + +RET parse_enum_value(PARAMS) { + INIT(enum_value); + obj->val = parse_name(ARGS); REQ + struct graphql_token *tok = list_top(used, struct graphql_token, node); + if (streq(tok->token_string, "true") + || streq(tok->token_string, "false") + || streq(tok->token_string, "null")) { + *err = "enum value cannot be true, false, or null"; + ROLLBACK(ARGS); + } + EXIT; +} + +RET parse_null_value(PARAMS) { + INIT(null_value); + obj->val = parse_keyword(ARGS, "null", "null expected"); + EXIT; +} + +RET parse_string_value(PARAMS) { + INIT(string_value); + obj->val = parse_string(ARGS); + EXIT; +} + +RET parse_boolean_value(PARAMS) { + INIT(boolean_value); + obj->val = parse_keyword(ARGS, "true", "invalid boolean value"); OR + obj->val = parse_keyword(ARGS, "false", "invalid boolean value"); + EXIT; +} + +RET parse_float_value(PARAMS) { + INIT(float_value); + obj->val = parse_float(ARGS); + EXIT; +} + +RET parse_int_value(PARAMS) { + INIT(int_value); + obj->val = parse_int(ARGS); + EXIT; +} + +RET parse_object_field(PARAMS) { + INIT(object_field); + obj->name = parse_name(ARGS); REQ + parse_punct(ARGS, ':'); REQ + obj->val = parse_value(ARGS); REQ + EXIT; +} + +RET parse_object_value(PARAMS) { + INIT(object_value); + parse_punct(ARGS, '{'); REQ + parse_punct(ARGS, '}'); + struct graphql_object_field *p = NULL; + while (*err) { + *err = NULL; + if (!p) { + obj->first = p = parse_object_field(ARGS); MSG("expected: object field or '}'"); REQ + } else { + p->next = parse_object_field(ARGS); MSG("expected: object field or '}'"); REQ + p = p->next; + } + parse_punct(ARGS, '}'); + } + EXIT; +} + +RET parse_default_value(PARAMS) { + INIT(default_value); + parse_punct(ARGS, '='); REQ + obj->val = parse_value(ARGS); REQ + EXIT; +} + +RET parse_value(PARAMS) { + INIT(value); + obj->var = parse_variable(ARGS); // FIXME: if not const + OR + obj->int_val = parse_int_value(ARGS); OR + obj->float_val = parse_float_value(ARGS); OR + obj->str_val = parse_string_value(ARGS); OR + obj->bool_val = parse_boolean_value(ARGS); OR + obj->null_val = parse_null_value(ARGS); OR + obj->enum_val = parse_enum_value(ARGS); OR + obj->list_val = parse_list_value(ARGS); OR + obj->obj_val = parse_object_value(ARGS); + EXIT; +} + +RET parse_type_condition(PARAMS) { + INIT(type_condition); + parse_keyword(ARGS, "on", "expected: 'on'"); REQ + obj->named_type = parse_named_type(ARGS); REQ + EXIT; +} + +RET parse_fragment_name(PARAMS) { + INIT(fragment_name); + obj->name = parse_name(ARGS); REQ + struct graphql_token *tok = list_top(used, struct graphql_token, node); + if (streq(tok->token_string, "on")) { + *err = "invalid fragment name"; + ROLLBACK(ARGS); + } + EXIT; +} + +RET parse_alias(PARAMS) { + INIT(alias); + obj->name = parse_name(ARGS); REQ + parse_punct(ARGS, ':'); REQ + EXIT; +} + +RET parse_argument(PARAMS) { + INIT(argument); + obj->name = parse_name(ARGS); REQ + parse_punct(ARGS, ':'); REQ + obj->val = parse_value(ARGS); REQ + EXIT; +} + +RET parse_arguments(PARAMS) { + INIT(arguments); + parse_punct(ARGS, '('); REQ + obj->first = parse_argument(ARGS); REQ + struct graphql_argument *p = obj->first; + parse_punct(ARGS, ')'); + while (*err) { + *err = NULL; + p->next = parse_argument(ARGS); MSG("expected: argument or ')'"); REQ; + p = p->next; + parse_punct(ARGS, ')'); + } + EXIT; +} + +RET parse_directive(PARAMS) { + INIT(directive); + parse_punct(ARGS, '@'); REQ + obj->name = parse_name(ARGS); REQ + obj->args = parse_arguments(ARGS); OPT + EXIT; +} + +RET parse_directives(PARAMS) { + INIT(directives); + obj->first = parse_directive(ARGS); REQ + struct graphql_directive *p = obj->first; + do { + p->next = parse_directive(ARGS); + p = p->next; + } WHILE_OPT; + EXIT; +} + +RET parse_fragment_spread(PARAMS) { + INIT(fragment_spread); + parse_punct(ARGS, PUNCT_SPREAD); REQ + obj->name = parse_fragment_name(ARGS); REQ + obj->directives = parse_directives(ARGS); OPT + EXIT; +} + +RET parse_variable_definition(PARAMS) { + INIT(variable_definition); + obj->var = parse_variable(ARGS); REQ + parse_punct(ARGS, ':'); REQ + obj->type = parse_type(ARGS); REQ + obj->default_val = parse_default_value(ARGS); OPT + obj->directives = parse_directives(ARGS); OPT + EXIT; +} + +RET parse_variable_definitions(PARAMS) { + INIT(variable_definitions); + parse_punct(ARGS, '('); REQ + obj->first = parse_variable_definition(ARGS); REQ + struct graphql_variable_definition *p = obj->first; + parse_punct(ARGS, ')'); + while (*err) { + *err = NULL; + p->next = parse_variable_definition(ARGS); MSG("expected: variable definition or ')'"); REQ + p = p->next; + parse_punct(ARGS, ')'); + } + EXIT; +} + +RET parse_selection_set(PARAMS); + +RET parse_fragment_definition(PARAMS) { + INIT(fragment_definition); + parse_keyword(ARGS, "fragment", "fragment expected"); REQ + obj->name = parse_fragment_name(ARGS); REQ + obj->type_cond = parse_type_condition(ARGS); REQ + obj->directives = parse_directives(ARGS); OPT + obj->sel_set = parse_selection_set(ARGS); REQ + EXIT; +} + +RET parse_inline_fragment(PARAMS) { + INIT(inline_fragment); + parse_punct(ARGS, PUNCT_SPREAD); REQ + obj->type_cond = parse_type_condition(ARGS); OPT + obj->directives = parse_directives(ARGS); OPT + obj->sel_set = parse_selection_set(ARGS); REQ + EXIT; +} + +RET parse_field(PARAMS) { + INIT(field); + obj->alias = parse_alias(ARGS); OPT + obj->name = parse_name(ARGS); REQ + obj->args = parse_arguments(ARGS); OPT + obj->directives = parse_directives(ARGS); OPT + obj->sel_set = parse_selection_set(ARGS); OPT + EXIT; +} + +RET parse_selection(PARAMS) { + INIT(selection); + obj->field = parse_field(ARGS); OR + obj->frag_spread = parse_fragment_spread(ARGS); OR + obj->inline_frag = parse_inline_fragment(ARGS); + MSG("expected: field, fragment spread, or inline fragment"); + EXIT; +} + +RET parse_selection_set(PARAMS) { + INIT(selection_set); + parse_punct(ARGS, '{'); REQ; + obj->first = parse_selection(ARGS); REQ; + struct graphql_selection *p = obj->first; + parse_punct(ARGS, '}'); + while (*err) { + *err = NULL; + p->next = parse_selection(ARGS); MSG("expected: selection or '}'"); REQ; + p = p->next; + parse_punct(ARGS, '}'); + } + EXIT; +} + +RET parse_operation_type(PARAMS) { + INIT(operation_type); + const char *errmsg = "expected: query, mutation, or subscription"; + obj->op_type = parse_keyword(ARGS, "query", errmsg); OR + obj->op_type = parse_keyword(ARGS, "mutation", errmsg); OR + obj->op_type = parse_keyword(ARGS, "subscription", errmsg); + EXIT; +} + +RET parse_operation_definition(PARAMS) { + INIT(operation_definition); + obj->op_type = parse_operation_type(ARGS); + if (!*err) { + obj->op_name = parse_name(ARGS); OPT + obj->vars = parse_variable_definitions(ARGS); OPT + obj->directives = parse_directives(ARGS); OPT + } else + *err = NULL; + obj->sel_set = parse_selection_set(ARGS); + if (*err) ROLLBACK(ARGS); + EXIT; +} + +RET parse_executable_definition(PARAMS) { + INIT(executable_definition); + obj->op_def = parse_operation_definition(ARGS); MSG("invalid operation or fragment definition"); OR + obj->frag_def = parse_fragment_definition(ARGS); MSG("invalid operation or fragment definition"); + EXIT; +} + +RET parse_executable_document(PARAMS) { + INIT(executable_document); + obj->first_def = parse_executable_definition(ARGS); REQ + struct graphql_executable_definition *p = obj->first_def; + do { + p->next_def = parse_executable_definition(ARGS); + p = p->next_def; + } WHILE_OPT; + EXIT; +} + +RET parse_definition(PARAMS) { + INIT(definition); + obj->executable_def = parse_executable_definition(ARGS); +/* OR + obj->type_system_def = parse_type_system_definition_or_extension(ARGS); + // NOTE: Optional type system is not (yet) implemented. +*/ + EXIT; +} + +RET parse_document(PARAMS) { + INIT(document); + obj->first_def = parse_definition(ARGS); REQ + struct graphql_definition *p = obj->first_def; + do { + p->next_def = parse_definition(ARGS); + p = p->next_def; + } WHILE_OPT; + EXIT; +} +void *currently_unused = parse_document; // to hide the warning till this is used + +/* Convert input string into tokens. + * + * All data (i.e. the list and the tokens it contains) are allocated to the + * specified tal context. + */ +const char *graphql_lex(const tal_t *ctx, const char *input, struct list_head **tokens) { + + unsigned int c; + const char *p, *line_beginning; + unsigned int line_num = 1; + struct list_head *tok_list; + struct graphql_token *tok; + + // Initialize token output list. + tok_list = tal(ctx, struct list_head); + if (tokens) + *tokens = tok_list; + list_head_init(tok_list); + + // Note: label and goto are used here like a continue statement except that + // it skips iteration, for when characters are fetched in the loop body. + p = input; + line_beginning = p; + do { + c = *p++; +newchar: + // Consume line terminators and increment line counter. + if (LINE_TERMINATOR(c)) { + unsigned int c0 = c; + c = *p++; + if (c0 == 10 || c0 == 13) + line_num++; + if (c0 == 13 && c == 10) + c = *p++; + line_beginning = p - 1; + goto newchar; + } + + // Consume other ignored tokens. + if (COMMA(c) || WHITE_SPACE(c)) { + c = *p++; + goto newchar; + } + if (COMMENT(c)) { + while (!EOF_CHAR(c) && COMMENT_CHAR(c)) + c = *p++; + goto newchar; + } + + // Return success when end is reached. + if (EOF_CHAR(c)) + return GRAPHQL_SUCCESS; + + // Punctuator tokens. + if (PUNCTUATOR(c)) { + + // Note beginning of token in input. + const char *start = p - 1; + + // Handle the ... multi-character case. + if (c == '.') { + c = *p++; + if (c != '.') + return "unrecognized punctuator"; + c = *p++; + if (c != '.') + return "unrecognized punctuator"; + c = PUNCT_SPREAD; + } + + tok = talz(tok_list, struct graphql_token); + list_add_tail(tok_list, &tok->node); + tok->token_type = c; + tok->token_string = NULL; + tok->source_line = line_num; + tok->source_column = start - line_beginning + 1; + tok->source_offset = start - input; + tok->source_len = p - start; + + } else if (NAME_START(c)) { + + // Name/identifier tokens. + tok = talz(tok_list, struct graphql_token); + list_add_tail(tok_list, &tok->node); + tok->token_type = 'a'; + // tok->token_string updated below. + tok->source_line = line_num; + tok->source_column = p - line_beginning; + // tok->source_len updated below. + + // Note the beginning of the name. + const char *name_begin = p - 1; + const char *name_end; + int name_len; + + // Consume the rest of the token. + do { + c = *p++; + } while (NAME_CONTINUE(c)); + + // Note the end of the name and calculate the length. + name_end = p - 1; + name_len = name_end - name_begin; + tok->source_offset = name_begin - input; + tok->source_len = name_len; + + // Copy the token string. + tok->token_string = tal_strndup(tok, name_begin, name_len); + + goto newchar; + + } else if (DIGIT(c) || c == '-') { + + // Number tokens. + const char *num_start = p - 1; + char type = 'i'; + + if (c == '-') { + c = *p++; + if (!DIGIT(c)) + return "negative sign must precede a number"; + } + + if (c == '0') { + c = *p++; + if (DIGIT(c)) + return "leading zeros are not allowed"; + } else { + do { + c = *p++; + } while(DIGIT(c)); + } + + if (c == '.') { + type = 'f'; + if (!DIGIT(*p)) + return "invalid float value fractional part"; + do { + c = *p++; + } while(DIGIT(c)); + } + + if (c == 'e' || c == 'E') { + type = 'f'; + c = *p++; + if (c == '+' || c == '-') + c = *p++; + if (!DIGIT(*p)) + return "invalid float value exponent part"; + do { + c = *p++; + } while(DIGIT(c)); + } + + if (c == '.' || NAME_START(c)) + return "invalid numeric value"; + + const char *num_end = p - 1; + int num_len = num_end - num_start; + + tok = talz(tok_list, struct graphql_token); + list_add_tail(tok_list, &tok->node); + tok->token_type = type; + tok->token_string = tal_strndup(tok, num_start, num_len); + tok->source_line = line_num; + tok->source_column = num_start - line_beginning + 1; + tok->source_offset = num_start - input; + tok->source_len = num_len; + + goto newchar; + + } else if (c == '"') { + + // String tokens. + c = *p++; + const char *str_begin = p - 1; + const char *str_end; + bool str_block = false; + if (c == '"') { + c = *p++; + if (c == '"') { + // block string + str_block = true; + str_begin += 2; + int quotes = 0; + do { + c = *p++; + if (c == '\"') quotes++; else quotes = 0; + if (quotes == 3 && *(p-4) == '\\') quotes = 0; + } while (BLOCK_STRING_CHAR(c) && quotes < 3); + if (quotes == 3) { + c = *--p; + c = *--p; + } + str_end = p - 1; + if (c != '"') + return "unterminated string or invalid character"; + c = *p++; + if (c != '"') + return "invalid string termination"; + c = *p++; + if (c != '"') + return "invalid string termination"; + } else { + // empty string + str_end = str_begin; + --p; + } + } else { + // normal string + --p; + do { + c = *p++; + if (c == '\\') { + c = *p++; + if (strchr("\"\\/bfnrtu", c)) { + if (c == 'u') { + c = *p++; + if (!HEX_DIGIT(c)) + return "invalid unicode escape sequence"; + c = *p++; + if (!HEX_DIGIT(c)) + return "invalid unicode escape sequence"; + c = *p++; + if (!HEX_DIGIT(c)) + return "invalid unicode escape sequence"; + c = *p++; + if (!HEX_DIGIT(c)) + return "invalid unicode escape sequence"; + } else { + c = 'a'; // anything besides a quote to let the loop continue + } + } else { + return "invalid string escape sequence"; + } + } + } while (STRING_CHAR(c)); + if (c != '"') + return "unterminated string or invalid character"; + str_end = p - 1; + } + int str_len = str_end - str_begin; + + tok = talz(tok_list, struct graphql_token); + list_add_tail(tok_list, &tok->node); + tok->token_type = 's'; + tok->token_string = tal_strndup(tok, str_begin, str_len); + tok->source_line = line_num; + tok->source_column = str_begin - line_beginning + 1; + tok->source_offset = str_begin - input; + tok->source_len = str_len; + + // Process escape sequences. These always shorten the string (so the memory allocation is always enough). + char d; + char *q = tok->token_string; + char *rewrite_dest; + int quotes = 0; + while ((d = *q++)) { + if (str_block) { + if (d == '\"') quotes++; else quotes = 0; + if (quotes == 3 && *(q-4) == '\\') { + quotes = 0; + rewrite_dest = q - 4; + cpystr(rewrite_dest, q - 3); + } + } else { + if (d == '\\') { + rewrite_dest = q - 1; + d = *q++; + switch (d) { + case '\"': + *rewrite_dest++ = '\"'; + cpystr(rewrite_dest, q--); + break; + case 'b': + *rewrite_dest++ = '\b'; + cpystr(rewrite_dest, q--); + break; + case 'f': + *rewrite_dest++ = '\f'; + cpystr(rewrite_dest, q--); + break; + case 'n': + *rewrite_dest++ = '\n'; + cpystr(rewrite_dest, q--); + break; + case 'r': + *rewrite_dest++ = '\r'; + cpystr(rewrite_dest, q--); + break; + case 't': + *rewrite_dest++ = '\t'; + cpystr(rewrite_dest, q--); + break; + case 'u': { + // Insert escaped character using UTF-8 multi-byte encoding. + char buf[5], *b = buf; + for (int i = 0; i < 4; i++) + *b++ = *q++; + *b = 0; + int code_point = strtol(buf, 0, 16); + int bytes = utf8_encode(code_point, rewrite_dest); + // note: if bytes == 0 + // due to encoding failure, + // the following will safely + // eliminate the invalid char. + rewrite_dest += bytes; + cpystr(rewrite_dest, q--); + } + break; + default: + cpystr(rewrite_dest, --q); + } + } + } + } + if (str_block) { + // Strip leading lines. + q = tok->token_string; + for (;;) { + d = *q++; + while (WHITE_SPACE(d)) + d = *q++; + if (LINE_TERMINATOR(d)) { + while (LINE_TERMINATOR(d)) + d = *q++; + cpystr(tok->token_string, q - 1); + q = tok->token_string; + } else + break; + } + + // Strip trailing lines. + q = tok->token_string + strlen(tok->token_string); + for (;;) { + d = *--q; + while (WHITE_SPACE(d)) + d = *--q; + if (LINE_TERMINATOR(d)) { + while (LINE_TERMINATOR(d)) + d = *--q; + *++q = 0; + } else + break; + } + + // Look for common indentation. + char *this_indent_start; + const char *this_indent_end; + const char *common_indent_start = NULL; + const char *common_indent_end = common_indent_start; + const char *r; + q = tok->token_string; + do { + d = *q++; + this_indent_start = q - 1; + while (WHITE_SPACE(d)) + d = *q++; + this_indent_end = q - 1; + if (LINE_TERMINATOR(d)) { + while (LINE_TERMINATOR(d)) + d = *q++; + continue; + } + if (EOF_CHAR(d)) + continue; + + if (common_indent_start == NULL) { + common_indent_start = this_indent_start; + common_indent_end = this_indent_end; + } + for (r = this_indent_start; r < this_indent_end && (r - this_indent_start + common_indent_start < common_indent_end); r++) { + if (*r != *(r - this_indent_start + common_indent_start)) + break; + } + common_indent_end = r - this_indent_start + common_indent_start; + + while (!LINE_TERMINATOR(d) && !EOF_CHAR(d)) + d = *q++; + while (LINE_TERMINATOR(d)) + d = *q++; + --q; + + } while (d); + + // Remove common indentation. + int common_indent_len = common_indent_end - common_indent_start; + if (common_indent_len > 0) { + q = tok->token_string; + do { + d = *q++; + this_indent_start = q - 1; + while (WHITE_SPACE(d)) + d = *q++; + this_indent_end = q - 1; + if (LINE_TERMINATOR(d)) { + while (LINE_TERMINATOR(d)) + d = *q++; + continue; + } + if (EOF_CHAR(d)) + continue; + + while (!LINE_TERMINATOR(d) && !EOF_CHAR(d)) + d = *q++; + --q; + + cpystr(this_indent_start, this_indent_start + common_indent_len); + q -= common_indent_len; + d = *q++; + + while (LINE_TERMINATOR(d)) + d = *q++; + --q; + + } while (d); + } + } + c = *p++; + goto newchar; + + } else { + return "invalid source character encountered"; + } + + } while (!EOF_CHAR(c)); + + return "unexpected end-of-input encountered"; +} + +// Convert lexed tokens into AST. +const char *graphql_parse(struct list_head *tokens, struct graphql_executable_document **doc) { + struct list_head used = LIST_HEAD_INIT(used); + const char *err = NULL; + *doc = parse_executable_document(tokens, &used, &err); + return err; +} + +// Convert input string into AST. +const char *graphql_lexparse(const tal_t *ctx, const char *input, struct list_head **tokens, struct graphql_executable_document **doc) { + const char *err = graphql_lex(ctx, input, tokens); + if (!err) + err = graphql_parse(*tokens, doc); + return err; +} + + + diff --git a/ccan/graphql/graphql.h b/ccan/graphql/graphql.h new file mode 100644 index 00000000..30116d46 --- /dev/null +++ b/ccan/graphql/graphql.h @@ -0,0 +1,291 @@ +/* MIT (BSD) license - see LICENSE file for details */ +#ifndef __GRAPHQL_H__ +#define __GRAPHQL_H__ 1 + +#include + +#include +#include + +// Coding constants +#define GRAPHQL_SUCCESS ((const char *)NULL) + +// The following structures constitute the AST returned by the parser. + +struct graphql_directive { + struct graphql_directive *next; + struct graphql_token *name; + struct graphql_arguments *args; + void *data; // for application use +}; + +struct graphql_directives { + struct graphql_directive *first; + void *data; // for application use +}; + +struct graphql_named_type { + struct graphql_token *name; + void *data; // for application use +}; + +struct graphql_type { + struct graphql_named_type *named; +// struct graphql_list_type *list; +// struct graphql_non_null_type *non_null; + void *data; // for application use +}; + +struct graphql_default_value { + struct graphql_value *val; + void *data; // for application use +}; + +struct graphql_variable_definition { + struct graphql_variable_definition *next; + struct graphql_variable *var; + struct graphql_type *type; + struct graphql_default_value *default_val; + struct graphql_directives *directives; + void *data; // for application use +}; + +struct graphql_variable_definitions { + struct graphql_variable_definition *first; + void *data; // for application use +}; + +struct graphql_variable { + struct graphql_token *name; + void *data; // for application use +}; + +struct graphql_object_field { + struct graphql_object_field *next; + struct graphql_token *name; + struct graphql_value *val; + void *data; // for application use +}; + +struct graphql_object_value { + struct graphql_object_field *first; + void *data; // for application use +}; + +struct graphql_list_value { + struct graphql_token *val; + void *data; // for application use +}; + +struct graphql_enum_value { + struct graphql_token *val; + void *data; // for application use +}; + +struct graphql_null_value { + struct graphql_token *val; + void *data; // for application use +}; + +struct graphql_string_value { + struct graphql_token *val; + void *data; // for application use +}; + +struct graphql_boolean_value { + struct graphql_token *val; + void *data; // for application use +}; + +struct graphql_float_value { + struct graphql_token *val; + void *data; // for application use +}; + +struct graphql_int_value { + struct graphql_token *val; + void *data; // for application use +}; + +struct graphql_value { + struct graphql_variable *var; + struct graphql_int_value *int_val; + struct graphql_float_value *float_val; + struct graphql_boolean_value *bool_val; + struct graphql_string_value *str_val; + struct graphql_null_value *null_val; + struct graphql_enum_value *enum_val; + struct graphql_list_value *list_val; + struct graphql_object_value *obj_val; + void *data; // for application use +}; + +struct graphql_inline_fragment { + struct graphql_type_condition *type_cond; + struct graphql_directives *directives; + struct graphql_selection_set *sel_set; + void *data; // for application use +}; + +struct graphql_type_condition { + struct graphql_named_type *named_type; + void *data; // for application use +}; + +struct graphql_fragment_name { + struct graphql_token *name; + void *data; // for application use +}; + +struct graphql_fragment_definition { + struct graphql_fragment_name *name; + struct graphql_type_condition *type_cond; + struct graphql_directives *directives; + struct graphql_selection_set *sel_set; + void *data; // for application use +}; + +struct graphql_fragment_spread { + struct graphql_fragment_name *name; + struct graphql_directives *directives; + void *data; // for application use +}; + +struct graphql_alias { + struct graphql_token *name; + void *data; // for application use +}; + +struct graphql_argument { + struct graphql_argument *next; + struct graphql_token *name; + struct graphql_value *val; + void *data; // for application use +}; + +struct graphql_arguments { + struct graphql_argument *first; + void *data; // for application use +}; + +struct graphql_field { + struct graphql_alias *alias; + struct graphql_token *name; + struct graphql_arguments *args; + struct graphql_directives *directives; + struct graphql_selection_set *sel_set; + void *data; // for application use +}; + +struct graphql_selection { + struct graphql_selection *next; + struct graphql_field *field; + struct graphql_fragment_spread *frag_spread; + struct graphql_inline_fragment *inline_frag; + void *data; // for application use +}; + +struct graphql_selection_set { + struct graphql_selection *first; + void *data; // for application use +}; + +struct graphql_operation_type { + struct graphql_token *op_type; + void *data; // for application use +}; + +struct graphql_operation_definition { + struct graphql_operation_type *op_type; + struct graphql_token *op_name; + struct graphql_variable_definitions *vars; + struct graphql_directives *directives; + struct graphql_selection_set *sel_set; + void *data; // for application use +}; + +struct graphql_executable_definition { + struct graphql_executable_definition *next_def; + struct graphql_operation_definition *op_def; + struct graphql_fragment_definition *frag_def; + void *data; // for application use +}; + +struct graphql_executable_document { + struct graphql_executable_definition *first_def; + void *data; // for application use +}; + +struct graphql_definition { + struct graphql_definition *next_def; + struct graphql_executable_definition *executable_def; + struct graphql_type_system_definition_or_extension *type_system_def; + void *data; // for application use +}; + +struct graphql_document { + struct graphql_definition *first_def; + void *data; // for application use +}; + +enum token_type_enum { + NAME = 'a', + INTEGER = 'i', + FLOAT = 'f', + STRING = 's', + PUNCT_BANG = '!', + PUNCT_SH__ = '$', + PUNCT_AMP = '&', + PUNCT_LPAR = '(', + PUNCT_RPAR = ')', + PUNCT_COLON = ':', + PUNCT_EQ = '=', + PUNCT_AT = '@', + PUNCT_LBRACKET = '[', + PUNCT_RBRACKET = ']', + PUNCT_LBRACE = '{', + PUNCT_PIPE = '|', + PUNCT_RBRACE = '}', + PUNCT_SPREAD = 0x2026, // spread operator (triple dot) +}; + +struct graphql_token { + struct list_node node; + enum token_type_enum token_type; + char *token_string; + unsigned int source_line; + unsigned int source_column; + unsigned int source_offset; + unsigned int source_len; + void *data; // for application use +}; + +/* The lexer. + * INPUTS: + * input - string to parse + * ctx - parent tal context or NULL + * tokens - a variable to receive the resulting token list + * RETURN: + * GRAPHQL_SUCCESS or an error string. + */ +const char *graphql_lex(const tal_t *ctx, const char *input, struct list_head **tokens); + +/* The parser. + * INPUTS: + * tokens - the list produced by the lexer + * doc - a variable to receive the resulting abstract syntax tree (AST) + * OPERATION: + * The token list is emptied during parsing, so far as the parsing + * succeeds. This allows the caller to inspect the line/char position + * of the next token (where the error likely is) and report that hint to + * the user in the form of an error message. + * RETURN: + * GRAPHQL_SUCCESS or an error string. + */ +const char *graphql_parse(struct list_head *tokens, struct graphql_executable_document **doc); + +/* The lexer and parser in one function, for convenience. */ +const char *graphql_lexparse(const tal_t *ctx, const char *input, struct list_head **tokens, struct graphql_executable_document **doc); + +#endif + diff --git a/ccan/graphql/test/run.c b/ccan/graphql/test/run.c new file mode 100644 index 00000000..7ae16784 --- /dev/null +++ b/ccan/graphql/test/run.c @@ -0,0 +1,3177 @@ +/* Include the C files directly. */ +#include "ccan/graphql/graphql.c" +#include "ccan/str/str.h" + +/* TEST POINT MACROS + * + * The idea here is to run each test case silently, and if any test point + * fails, that test case is re-run verbosely to pinpoint the failure. + * + * RUN macros run a whole test case function. + * + * Different TEST_xxxx macros are provided for different test point types as + * follows: + * + * TEST_CONT - Test and continue testing regardless of failure (the failure + * will still be printed). + * TEST_ABRT - Test and abort the test case, used when testing pointer + * validity to avoid subsequent dereference errors. + * TEST_STRG - Test a string value, which includes the convenience of testing + * for a null pointer. + */ + +#define RUN(test) { prev_fail = fail; test(source); if (fail != prev_fail) { mute = false; test(source); mute = true; } } +#define RUN1(test,arg) { prev_fail = fail; test(source,arg); if (fail != prev_fail) { mute = false; test(source,arg); mute = true; } } +#define RUN2(test,arg1,arg2) { prev_fail = fail; test(source,arg1,arg2); if (fail != prev_fail) { mute = false; test(source,arg1,arg2); mute = true; } } + +#define TEST_CONT(expr) { bool c = (expr); if (mute) c? pass++ : fail++; else printf("%s: %s\033[0m\n", c? "passed" : "\033[91mfailed", stringify(expr)); } +#define TEST_ABRT(expr) { bool c = (expr); if (mute) c? pass++ : fail++; else printf("%s: %s\033[0m\n", c? "passed" : "\033[91mfailed", stringify(expr)); if (!c) return; } +#define TEST_STRG(str,expr) { bool c = ((str) && streq((str),(expr))); if (mute) c? pass++ : fail++; else printf("%s: %s == %s\033[0m\n", c? "passed" : "\033[91mfailed", stringify(str), stringify(expr)); if (!c) return; } + +// Global variables to track overall results. +int pass = 0, fail = 0; +bool mute = 1; + +// Helper function. +int listlen(struct list_head *tokens); +int listlen(struct list_head *tokens) { + struct graphql_token *tok; + int n=0; + list_for_each(tokens, tok, node) { + n++; + } + return n; +} + +/* Test case function prototypes */ + +void check_example_3(const char *source); +void check_example_5(char *source); +void check_example_6(char *source); +void check_example_7(char *source); +void check_example_8(char *source); +void check_example_9(char *source); +void check_example_10(char *source); +void check_example_11(char *source); +void check_example_12_and_13(const char *source); +void check_example_14(char *source); +void check_example_16(char *source); +void check_example_18(char *source); +void check_example_19(char *source); +void check_example_20(char *source); +void check_example_21(char *source); +void check_example_23(char *source); +void check_example_24(char *source); +void check_int_value(char *source, int int_value); +void check_invalid_int_values(char *source); +void check_float_value(char *source, float float_value, const char *format); +void check_valid_float_values(char *source); +void check_invalid_float_values(char *source); +void check_boolean_values(char *source); +void check_string_value(char *source, const char *test_value, const char *expected_result); +void check_example_25_and_26(const char *source); +void check_example_29(char *source); +void check_example_30_and_31(const char *source); +void check_example_32(char *source); +void check_example_34(char *source); +void check_example_35(char *source); + +/* Test case functions begin here, called by main(). + * Note: Memory should be freed correctly in the success case, but if there + * are errors, all bets are off. + */ + +void check_example_3(const char *source) { + struct list_head *tokens; + struct graphql_token *tok; + + if (!mute) printf("// Example No. 3\n"); + + // Test the lexer. + TEST_CONT(graphql_lex(NULL, source, &tokens) == NULL); + TEST_ABRT(listlen(tokens) == 11); + tok = list_pop(tokens, struct graphql_token, node); + TEST_CONT(tok->token_type == '{'); + TEST_CONT(tok->source_line == 1); + TEST_CONT(tok->source_column == 1); + TEST_CONT(tok->source_len == 1); + tok = list_pop(tokens, struct graphql_token, node); + TEST_CONT(tok->token_type == 'a'); + TEST_STRG(tok->token_string, "user"); + TEST_CONT(tok->source_line == 2); + TEST_CONT(tok->source_column == 3); + TEST_CONT(tok->source_len == 4); + tok = list_pop(tokens, struct graphql_token, node); + TEST_CONT(tok->token_type == '('); + TEST_CONT(tok->source_line == 2); + TEST_CONT(tok->source_column == 7); + TEST_CONT(tok->source_len == 1); + tok = list_pop(tokens, struct graphql_token, node); + TEST_CONT(tok->token_type == 'a'); + TEST_STRG(tok->token_string, "id"); + TEST_CONT(tok->source_line == 2); + TEST_CONT(tok->source_column == 8); + TEST_CONT(tok->source_len == 2); + tok = list_pop(tokens, struct graphql_token, node); + TEST_CONT(tok->token_type == ':'); + TEST_CONT(tok->source_line == 2); + TEST_CONT(tok->source_column == 10); + TEST_CONT(tok->source_len == 1); + tok = list_pop(tokens, struct graphql_token, node); + TEST_CONT(tok->token_type == 'i'); + TEST_STRG(tok->token_string, "4"); + TEST_CONT(tok->source_line == 2); + TEST_CONT(tok->source_column == 12); + TEST_CONT(tok->source_len == 1); + tok = list_pop(tokens, struct graphql_token, node); + TEST_CONT(tok->token_type == ')'); + TEST_CONT(tok->source_line == 2); + TEST_CONT(tok->source_column == 13); + TEST_CONT(tok->source_len == 1); + tok = list_pop(tokens, struct graphql_token, node); + TEST_CONT(tok->token_type == '{'); + TEST_CONT(tok->source_line == 2); + TEST_CONT(tok->source_column == 15); + TEST_CONT(tok->source_len == 1); + tok = list_pop(tokens, struct graphql_token, node); + TEST_CONT(tok->token_type == 'a'); + TEST_STRG(tok->token_string, "name"); + TEST_CONT(tok->source_line == 3); + TEST_CONT(tok->source_column == 5); + TEST_CONT(tok->source_len == 4); + tok = list_pop(tokens, struct graphql_token, node); + TEST_CONT(tok->token_type == '}'); + TEST_CONT(tok->source_line == 4); + TEST_CONT(tok->source_column == 3); + TEST_CONT(tok->source_len == 1); + tok = list_pop(tokens, struct graphql_token, node); + TEST_CONT(tok->token_type == '}'); + TEST_CONT(tok->source_line == 5); + TEST_CONT(tok->source_column == 1); + TEST_CONT(tok->source_len == 1); + tok = list_pop(tokens, struct graphql_token, node); + TEST_CONT(tok == NULL); + tokens = tal_free(tokens); + + // Test the parser. + struct graphql_executable_document *doc; + TEST_CONT(graphql_lex(NULL, source, &tokens) == NULL); + TEST_ABRT(graphql_parse(tokens, &doc) == NULL); + TEST_ABRT(doc != NULL); + TEST_ABRT(doc->first_def != NULL); + TEST_CONT(doc->first_def->next_def == NULL); + TEST_CONT(doc->first_def->frag_def == NULL); + TEST_ABRT(doc->first_def->op_def != NULL); + TEST_CONT(doc->first_def->op_def->op_type == NULL); + TEST_CONT(doc->first_def->op_def->op_name == NULL); + TEST_CONT(doc->first_def->op_def->vars == NULL); + TEST_CONT(doc->first_def->op_def->directives == NULL); + TEST_ABRT(doc->first_def->op_def->sel_set != NULL); + TEST_ABRT(doc->first_def->op_def->sel_set->first != NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->next == NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->frag_spread == NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->inline_frag == NULL); + TEST_ABRT(doc->first_def->op_def->sel_set->first->field != NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->alias == NULL); + TEST_ABRT(doc->first_def->op_def->sel_set->first->field->name != NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->name->token_type == 'a'); + TEST_STRG(doc->first_def->op_def->sel_set->first->field->name->token_string, "user"); + TEST_ABRT(doc->first_def->op_def->sel_set->first->field->args != NULL); + TEST_ABRT(doc->first_def->op_def->sel_set->first->field->args->first != NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->args->first->next == NULL); + TEST_ABRT(doc->first_def->op_def->sel_set->first->field->args->first->name != NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->args->first->name->token_type == 'a'); + TEST_STRG(doc->first_def->op_def->sel_set->first->field->args->first->name->token_string, "id"); + TEST_ABRT(doc->first_def->op_def->sel_set->first->field->args->first->val != NULL); + TEST_ABRT(doc->first_def->op_def->sel_set->first->field->args->first->val->int_val != NULL); + TEST_ABRT(doc->first_def->op_def->sel_set->first->field->args->first->val->int_val->val != NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->args->first->val->int_val->val->token_type == 'i'); + TEST_STRG(doc->first_def->op_def->sel_set->first->field->args->first->val->int_val->val->token_string, "4"); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->directives == NULL); + TEST_ABRT(doc->first_def->op_def->sel_set->first->field->sel_set != NULL); + TEST_ABRT(doc->first_def->op_def->sel_set->first->field->sel_set->first != NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->sel_set->first->next == NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->sel_set->first->frag_spread == NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->sel_set->first->inline_frag == NULL); + TEST_ABRT(doc->first_def->op_def->sel_set->first->field->sel_set->first->field != NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->sel_set->first->field->alias == NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->sel_set->first->field->args == NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->sel_set->first->field->directives == NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->sel_set->first->field->sel_set == NULL); + TEST_ABRT(doc->first_def->op_def->sel_set->first->field->sel_set->first->field->name != NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->sel_set->first->field->name->token_type == 'a'); + TEST_STRG(doc->first_def->op_def->sel_set->first->field->sel_set->first->field->name->token_string, "name"); + tokens = tal_free(tokens); +} + +void check_example_5(char *source) { + struct list_head *tokens; + struct graphql_token *tok; + + if (!mute) printf("// Example No. 5\n"); + + sprintf(source, "\ +mutation {\n\ + likeStory(storyID: 12345) {\n\ + story {\n\ + likeCount\n\ + }\n\ + }\n\ +}\n\ + "); + + // Test the lexer. + TEST_CONT(graphql_lex(NULL, source, &tokens) == NULL); + TEST_ABRT(listlen(tokens) == 15); + tok = list_pop(tokens, struct graphql_token, node); + TEST_CONT(tok->token_type == 'a'); + TEST_STRG(tok->token_string, "mutation"); + TEST_CONT(tok->source_line == 1); + TEST_CONT(tok->source_column == 1); + TEST_CONT(tok->source_len == 8); + tok = list_pop(tokens, struct graphql_token, node); + TEST_CONT(tok->token_type == '{'); + tok = list_pop(tokens, struct graphql_token, node); + TEST_CONT(tok->token_type == 'a'); + TEST_STRG(tok->token_string, "likeStory"); + TEST_CONT(tok->source_line == 2); + TEST_CONT(tok->source_column == 3); + TEST_CONT(tok->source_len == 9); + tok = list_pop(tokens, struct graphql_token, node); + TEST_CONT(tok->token_type == '('); + tok = list_pop(tokens, struct graphql_token, node); + TEST_CONT(tok->token_type == 'a'); + TEST_STRG(tok->token_string, "storyID"); + TEST_CONT(tok->source_line == 2); + tok = list_pop(tokens, struct graphql_token, node); + TEST_CONT(tok->token_type == ':'); + tok = list_pop(tokens, struct graphql_token, node); + TEST_CONT(tok->token_type == 'i'); + TEST_STRG(tok->token_string, "12345"); + TEST_CONT(tok->source_line == 2); + TEST_CONT(tok->source_column == 22); + TEST_CONT(tok->source_len == 5); + tok = list_pop(tokens, struct graphql_token, node); + TEST_CONT(tok->token_type == ')'); + tok = list_pop(tokens, struct graphql_token, node); + TEST_CONT(tok->token_type == '{'); + tok = list_pop(tokens, struct graphql_token, node); + TEST_CONT(tok->token_type == 'a'); + TEST_STRG(tok->token_string, "story"); + TEST_CONT(tok->source_line == 3); + tok = list_pop(tokens, struct graphql_token, node); + TEST_CONT(tok->token_type == '{'); + tok = list_pop(tokens, struct graphql_token, node); + TEST_CONT(tok->token_type == 'a'); + TEST_STRG(tok->token_string, "likeCount"); + TEST_CONT(tok->source_line == 4); + tok = list_pop(tokens, struct graphql_token, node); + TEST_CONT(tok->token_type == '}'); + tok = list_pop(tokens, struct graphql_token, node); + TEST_CONT(tok->token_type == '}'); + tok = list_pop(tokens, struct graphql_token, node); + TEST_CONT(tok->token_type == '}'); + tok = list_pop(tokens, struct graphql_token, node); + TEST_CONT(tok == NULL); + tokens = tal_free(tokens); + + // Test the parser. + struct graphql_executable_document *doc; + TEST_CONT(graphql_lex(NULL, source, &tokens) == NULL); + TEST_ABRT(graphql_parse(tokens, &doc) == NULL); + TEST_ABRT(doc != NULL); + TEST_ABRT(doc->first_def != NULL); + TEST_CONT(doc->first_def->next_def == NULL); + TEST_CONT(doc->first_def->frag_def == NULL); + TEST_ABRT(doc->first_def->op_def != NULL); + TEST_ABRT(doc->first_def->op_def->op_type != NULL); + TEST_ABRT(doc->first_def->op_def->op_type->op_type != NULL); + TEST_CONT(doc->first_def->op_def->op_type->op_type->token_type == 'a'); + TEST_STRG(doc->first_def->op_def->op_type->op_type->token_string, "mutation"); + TEST_ABRT(doc->first_def->op_def->op_name == NULL); + TEST_CONT(doc->first_def->op_def->vars == NULL); + TEST_CONT(doc->first_def->op_def->directives == NULL); + TEST_ABRT(doc->first_def->op_def->sel_set != NULL); + TEST_ABRT(doc->first_def->op_def->sel_set->first != NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->next == NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->frag_spread == NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->inline_frag == NULL); + TEST_ABRT(doc->first_def->op_def->sel_set->first->field != NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->alias == NULL); + TEST_ABRT(doc->first_def->op_def->sel_set->first->field->name != NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->name->token_type == 'a'); + TEST_STRG(doc->first_def->op_def->sel_set->first->field->name->token_string, "likeStory"); + TEST_ABRT(doc->first_def->op_def->sel_set->first->field->args != NULL); + TEST_ABRT(doc->first_def->op_def->sel_set->first->field->args->first != NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->args->first->next == NULL); + TEST_ABRT(doc->first_def->op_def->sel_set->first->field->args->first->name != NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->args->first->name->token_type == 'a'); + TEST_STRG(doc->first_def->op_def->sel_set->first->field->args->first->name->token_string, "storyID"); + TEST_ABRT(doc->first_def->op_def->sel_set->first->field->args->first->val != NULL); + TEST_ABRT(doc->first_def->op_def->sel_set->first->field->args->first->val->int_val != NULL); + TEST_ABRT(doc->first_def->op_def->sel_set->first->field->args->first->val->int_val->val != NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->args->first->val->int_val->val->token_type == 'i'); + TEST_STRG(doc->first_def->op_def->sel_set->first->field->args->first->val->int_val->val->token_string, "12345"); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->directives == NULL); + TEST_ABRT(doc->first_def->op_def->sel_set->first->field->sel_set != NULL); + TEST_ABRT(doc->first_def->op_def->sel_set->first->field->sel_set->first != NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->sel_set->first->next == NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->sel_set->first->frag_spread == NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->sel_set->first->inline_frag == NULL); + TEST_ABRT(doc->first_def->op_def->sel_set->first->field->sel_set->first->field != NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->sel_set->first->field->alias == NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->sel_set->first->field->args == NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->sel_set->first->field->directives == NULL); + TEST_ABRT(doc->first_def->op_def->sel_set->first->field->sel_set->first->field->name != NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->sel_set->first->field->name->token_type == 'a'); + TEST_STRG(doc->first_def->op_def->sel_set->first->field->sel_set->first->field->name->token_string, "story"); + TEST_ABRT(doc->first_def->op_def->sel_set->first->field->sel_set->first->field->sel_set != NULL); + TEST_ABRT(doc->first_def->op_def->sel_set->first->field->sel_set->first->field->sel_set->first != NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->sel_set->first->field->sel_set->first->next == NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->sel_set->first->field->sel_set->first->frag_spread == NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->sel_set->first->field->sel_set->first->inline_frag == NULL); + TEST_ABRT(doc->first_def->op_def->sel_set->first->field->sel_set->first->field->sel_set->first->field != NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->sel_set->first->field->sel_set->first->field->alias == NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->sel_set->first->field->sel_set->first->field->args == NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->sel_set->first->field->sel_set->first->field->directives == NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->sel_set->first->field->sel_set->first->field->sel_set == NULL); + TEST_ABRT(doc->first_def->op_def->sel_set->first->field->sel_set->first->field->sel_set->first->field->name != NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->sel_set->first->field->sel_set->first->field->name->token_type == 'a'); + TEST_STRG(doc->first_def->op_def->sel_set->first->field->sel_set->first->field->sel_set->first->field->name->token_string, "likeCount"); + tokens = tal_free(tokens); +} + +void check_example_6(char *source) { + struct list_head *tokens; + struct graphql_token *tok; + + if (!mute) printf("// Example No. 6\n"); + + sprintf(source, "\ +{\n\ + field\n\ +}\n\ + "); + + // Test the lexer. + TEST_CONT(graphql_lex(NULL, source, &tokens) == NULL); + TEST_ABRT(listlen(tokens) == 3); + tok = list_pop(tokens, struct graphql_token, node); + TEST_CONT(tok->token_type == '{'); + tok = list_pop(tokens, struct graphql_token, node); + TEST_CONT(tok->token_type == 'a'); + TEST_STRG(tok->token_string, "field"); + TEST_CONT(tok->source_line == 2); + TEST_CONT(tok->source_column == 3); + TEST_CONT(tok->source_len == 5); + tok = list_pop(tokens, struct graphql_token, node); + TEST_CONT(tok->token_type == '}'); + tok = list_pop(tokens, struct graphql_token, node); + TEST_CONT(tok == NULL); + tokens = tal_free(tokens); + + // Test the parser. + struct graphql_executable_document *doc; + TEST_CONT(graphql_lex(NULL, source, &tokens) == NULL); + TEST_ABRT(graphql_parse(tokens, &doc) == NULL); + TEST_ABRT(doc != NULL); + TEST_ABRT(doc->first_def != NULL); + TEST_CONT(doc->first_def->next_def == NULL); + TEST_CONT(doc->first_def->frag_def == NULL); + TEST_ABRT(doc->first_def->op_def != NULL); + TEST_CONT(doc->first_def->op_def->op_type == NULL); + TEST_ABRT(doc->first_def->op_def->op_name == NULL); + TEST_CONT(doc->first_def->op_def->vars == NULL); + TEST_CONT(doc->first_def->op_def->directives == NULL); + TEST_ABRT(doc->first_def->op_def->sel_set != NULL); + TEST_ABRT(doc->first_def->op_def->sel_set->first != NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->next == NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->frag_spread == NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->inline_frag == NULL); + TEST_ABRT(doc->first_def->op_def->sel_set->first->field != NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->alias == NULL); + TEST_ABRT(doc->first_def->op_def->sel_set->first->field->name != NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->name->token_type == 'a'); + TEST_STRG(doc->first_def->op_def->sel_set->first->field->name->token_string, "field"); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->args == NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->directives == NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->sel_set == NULL); + tokens = tal_free(tokens); +} + +void check_example_7(char *source) { + struct list_head *tokens; + struct graphql_token *tok; + + if (!mute) printf("// Example No. 7\n"); + + sprintf(source, "\ +{\n\ + id\n\ + firstName\n\ + lastName\n\ +}\n\ + "); + + // Test the lexer. + TEST_CONT(graphql_lex(NULL, source, &tokens) == NULL); + TEST_ABRT(listlen(tokens) == 5); + tok = list_pop(tokens, struct graphql_token, node); + TEST_CONT(tok->token_type == '{'); + tok = list_pop(tokens, struct graphql_token, node); + TEST_CONT(tok->token_type == 'a'); + TEST_STRG(tok->token_string, "id"); + TEST_CONT(tok->source_line == 2); + TEST_CONT(tok->source_column == 3); + TEST_CONT(tok->source_len == 2); + tok = list_pop(tokens, struct graphql_token, node); + TEST_CONT(tok->token_type == 'a'); + TEST_STRG(tok->token_string, "firstName"); + TEST_CONT(tok->source_line == 3); + TEST_CONT(tok->source_column == 3); + TEST_CONT(tok->source_len == 9); + tok = list_pop(tokens, struct graphql_token, node); + TEST_CONT(tok->token_type == 'a'); + TEST_STRG(tok->token_string, "lastName"); + TEST_CONT(tok->source_line == 4); + TEST_CONT(tok->source_column == 3); + TEST_CONT(tok->source_len == 8); + tok = list_pop(tokens, struct graphql_token, node); + TEST_CONT(tok->token_type == '}'); + tok = list_pop(tokens, struct graphql_token, node); + TEST_CONT(tok == NULL); + tokens = tal_free(tokens); + + // Test the parser. + struct graphql_executable_document *doc; + TEST_CONT(graphql_lex(NULL, source, &tokens) == NULL); + TEST_ABRT(graphql_parse(tokens, &doc) == NULL); + TEST_ABRT(doc != NULL); + TEST_ABRT(doc->first_def != NULL); + TEST_CONT(doc->first_def->next_def == NULL); + TEST_CONT(doc->first_def->frag_def == NULL); + TEST_ABRT(doc->first_def->op_def != NULL); + TEST_CONT(doc->first_def->op_def->op_type == NULL); + TEST_ABRT(doc->first_def->op_def->op_name == NULL); + TEST_CONT(doc->first_def->op_def->vars == NULL); + TEST_CONT(doc->first_def->op_def->directives == NULL); + TEST_ABRT(doc->first_def->op_def->sel_set != NULL); + TEST_ABRT(doc->first_def->op_def->sel_set->first != NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->frag_spread == NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->inline_frag == NULL); + TEST_ABRT(doc->first_def->op_def->sel_set->first->field != NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->alias == NULL); + TEST_ABRT(doc->first_def->op_def->sel_set->first->field->name != NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->name->token_type == 'a'); + TEST_STRG(doc->first_def->op_def->sel_set->first->field->name->token_string, "id"); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->args == NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->directives == NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->sel_set == NULL); + TEST_ABRT(doc->first_def->op_def->sel_set->first->next != NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->next->frag_spread == NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->next->inline_frag == NULL); + TEST_ABRT(doc->first_def->op_def->sel_set->first->next->field != NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->next->field->alias == NULL); + TEST_ABRT(doc->first_def->op_def->sel_set->first->next->field->name != NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->next->field->name->token_type == 'a'); + TEST_STRG(doc->first_def->op_def->sel_set->first->next->field->name->token_string, "firstName"); + TEST_CONT(doc->first_def->op_def->sel_set->first->next->field->args == NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->next->field->directives == NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->next->field->sel_set == NULL); + TEST_ABRT(doc->first_def->op_def->sel_set->first->next->next != NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->next->next->frag_spread == NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->next->next->inline_frag == NULL); + TEST_ABRT(doc->first_def->op_def->sel_set->first->next->next->field != NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->next->next->field->alias == NULL); + TEST_ABRT(doc->first_def->op_def->sel_set->first->next->next->field->name != NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->next->next->field->name->token_type == 'a'); + TEST_STRG(doc->first_def->op_def->sel_set->first->next->next->field->name->token_string, "lastName"); + TEST_CONT(doc->first_def->op_def->sel_set->first->next->next->field->args == NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->next->next->field->directives == NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->next->next->field->sel_set == NULL); + TEST_ABRT(doc->first_def->op_def->sel_set->first->next->next->next == NULL); + tokens = tal_free(tokens); +} + +void check_example_8(char *source) { + struct list_head *tokens; + struct graphql_token *tok; + + if (!mute) printf("// Example No. 8\n"); + + sprintf(source, "\ +{\n\ + me {\n\ + id\n\ + firstName\n\ + lastName\n\ + birthday {\n\ + month\n\ + day\n\ + }\n\ + friends {\n\ + name\n\ + }\n\ + }\n\ +}\n\ + "); + + // Test the lexer. + TEST_CONT(graphql_lex(NULL, source, &tokens) == NULL); + TEST_ABRT(listlen(tokens) == 17); + tok = list_pop(tokens, struct graphql_token, node); TEST_CONT(tok->token_type == '{'); + tok = list_pop(tokens, struct graphql_token, node); TEST_CONT(tok->token_type == 'a'); + TEST_STRG(tok->token_string, "me"); + tok = list_pop(tokens, struct graphql_token, node); TEST_CONT(tok->token_type == '{'); + tok = list_pop(tokens, struct graphql_token, node); TEST_CONT(tok->token_type == 'a'); + TEST_STRG(tok->token_string, "id"); + tok = list_pop(tokens, struct graphql_token, node); TEST_CONT(tok->token_type == 'a'); + TEST_STRG(tok->token_string, "firstName"); + tok = list_pop(tokens, struct graphql_token, node); TEST_CONT(tok->token_type == 'a'); + TEST_STRG(tok->token_string, "lastName"); + tok = list_pop(tokens, struct graphql_token, node); TEST_CONT(tok->token_type == 'a'); + TEST_STRG(tok->token_string, "birthday"); + tok = list_pop(tokens, struct graphql_token, node); TEST_CONT(tok->token_type == '{'); + tok = list_pop(tokens, struct graphql_token, node); TEST_CONT(tok->token_type == 'a'); + TEST_STRG(tok->token_string, "month"); + tok = list_pop(tokens, struct graphql_token, node); TEST_CONT(tok->token_type == 'a'); + TEST_STRG(tok->token_string, "day"); + tok = list_pop(tokens, struct graphql_token, node); TEST_CONT(tok->token_type == '}'); + tok = list_pop(tokens, struct graphql_token, node); TEST_CONT(tok->token_type == 'a'); + TEST_STRG(tok->token_string, "friends"); + tok = list_pop(tokens, struct graphql_token, node); TEST_CONT(tok->token_type == '{'); + tok = list_pop(tokens, struct graphql_token, node); TEST_CONT(tok->token_type == 'a'); + TEST_STRG(tok->token_string, "name"); + tok = list_pop(tokens, struct graphql_token, node); TEST_CONT(tok->token_type == '}'); + tok = list_pop(tokens, struct graphql_token, node); TEST_CONT(tok->token_type == '}'); + tok = list_pop(tokens, struct graphql_token, node); TEST_CONT(tok->token_type == '}'); + tok = list_pop(tokens, struct graphql_token, node); TEST_CONT(tok == NULL); + tokens = tal_free(tokens); + + // Test the parser. + struct graphql_executable_document *doc; + TEST_CONT(graphql_lex(NULL, source, &tokens) == NULL); + TEST_ABRT(graphql_parse(tokens, &doc) == NULL); + TEST_ABRT(doc != NULL); + TEST_ABRT(doc->first_def != NULL); + TEST_CONT(doc->first_def->next_def == NULL); + TEST_CONT(doc->first_def->frag_def == NULL); + TEST_ABRT(doc->first_def->op_def != NULL); + TEST_CONT(doc->first_def->op_def->op_type == NULL); + TEST_ABRT(doc->first_def->op_def->op_name == NULL); + TEST_CONT(doc->first_def->op_def->vars == NULL); + TEST_CONT(doc->first_def->op_def->directives == NULL); + TEST_ABRT(doc->first_def->op_def->sel_set != NULL); + TEST_ABRT(doc->first_def->op_def->sel_set->first != NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->next == NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->frag_spread == NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->inline_frag == NULL); + TEST_ABRT(doc->first_def->op_def->sel_set->first->field != NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->alias == NULL); + TEST_ABRT(doc->first_def->op_def->sel_set->first->field->name != NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->name->token_type == 'a'); + TEST_STRG(doc->first_def->op_def->sel_set->first->field->name->token_string, "me"); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->args == NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->directives == NULL); + TEST_ABRT(doc->first_def->op_def->sel_set->first->field->sel_set != NULL); + TEST_ABRT(doc->first_def->op_def->sel_set->first->field->sel_set->first != NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->sel_set->first->frag_spread == NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->sel_set->first->inline_frag == NULL); + TEST_ABRT(doc->first_def->op_def->sel_set->first->field->sel_set->first->field != NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->sel_set->first->field->alias == NULL); + TEST_ABRT(doc->first_def->op_def->sel_set->first->field->sel_set->first->field->name != NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->sel_set->first->field->name->token_type == 'a'); + TEST_STRG(doc->first_def->op_def->sel_set->first->field->sel_set->first->field->name->token_string, "id"); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->sel_set->first->field->args == NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->sel_set->first->field->directives == NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->sel_set->first->field->sel_set == NULL); + TEST_ABRT(doc->first_def->op_def->sel_set->first->field->sel_set->first->next != NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->sel_set->first->next->frag_spread == NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->sel_set->first->next->inline_frag == NULL); + TEST_ABRT(doc->first_def->op_def->sel_set->first->field->sel_set->first->next->field != NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->sel_set->first->next->field->alias == NULL); + TEST_ABRT(doc->first_def->op_def->sel_set->first->field->sel_set->first->next->field->name != NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->sel_set->first->next->field->name->token_type == 'a'); + TEST_STRG(doc->first_def->op_def->sel_set->first->field->sel_set->first->next->field->name->token_string, "firstName"); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->sel_set->first->next->field->args == NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->sel_set->first->next->field->directives == NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->sel_set->first->next->field->sel_set == NULL); + TEST_ABRT(doc->first_def->op_def->sel_set->first->field->sel_set->first->next->next != NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->sel_set->first->next->next->frag_spread == NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->sel_set->first->next->next->inline_frag == NULL); + TEST_ABRT(doc->first_def->op_def->sel_set->first->field->sel_set->first->next->next->field != NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->sel_set->first->next->next->field->alias == NULL); + TEST_ABRT(doc->first_def->op_def->sel_set->first->field->sel_set->first->next->next->field->name != NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->sel_set->first->next->next->field->name->token_type == 'a'); + TEST_STRG(doc->first_def->op_def->sel_set->first->field->sel_set->first->next->next->field->name->token_string, "lastName"); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->sel_set->first->next->next->field->args == NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->sel_set->first->next->next->field->directives == NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->sel_set->first->next->next->field->sel_set == NULL); + TEST_ABRT(doc->first_def->op_def->sel_set->first->field->sel_set->first->next->next->next != NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->sel_set->first->next->next->next->frag_spread == NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->sel_set->first->next->next->next->inline_frag == NULL); + TEST_ABRT(doc->first_def->op_def->sel_set->first->field->sel_set->first->next->next->next->field != NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->sel_set->first->next->next->next->field->alias == NULL); + TEST_ABRT(doc->first_def->op_def->sel_set->first->field->sel_set->first->next->next->next->field->name != NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->sel_set->first->next->next->next->field->name->token_type == 'a'); + TEST_STRG(doc->first_def->op_def->sel_set->first->field->sel_set->first->next->next->next->field->name->token_string, "birthday"); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->sel_set->first->next->next->next->field->args == NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->sel_set->first->next->next->next->field->directives == NULL); + TEST_ABRT(doc->first_def->op_def->sel_set->first->field->sel_set->first->next->next->next->field->sel_set != NULL); + TEST_ABRT(doc->first_def->op_def->sel_set->first->field->sel_set->first->next->next->next->field->sel_set->first != NULL); + TEST_ABRT(doc->first_def->op_def->sel_set->first->field->sel_set->first->next->next->next->field->sel_set->first->field != NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->sel_set->first->next->next->next->field->sel_set->first->field->alias == NULL); + TEST_ABRT(doc->first_def->op_def->sel_set->first->field->sel_set->first->next->next->next->field->sel_set->first->field->name != NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->sel_set->first->next->next->next->field->sel_set->first->field->name->token_type == 'a'); + TEST_STRG(doc->first_def->op_def->sel_set->first->field->sel_set->first->next->next->next->field->sel_set->first->field->name->token_string, "month"); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->sel_set->first->next->next->next->field->sel_set->first->field->args == NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->sel_set->first->next->next->next->field->sel_set->first->field->directives == NULL); + TEST_ABRT(doc->first_def->op_def->sel_set->first->field->sel_set->first->next->next->next->field->sel_set->first->field->sel_set == NULL); + TEST_ABRT(doc->first_def->op_def->sel_set->first->field->sel_set->first->next->next->next->field->sel_set->first->next != NULL); + TEST_ABRT(doc->first_def->op_def->sel_set->first->field->sel_set->first->next->next->next->field->sel_set->first->next->field != NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->sel_set->first->next->next->next->field->sel_set->first->next->field->alias == NULL); + TEST_ABRT(doc->first_def->op_def->sel_set->first->field->sel_set->first->next->next->next->field->sel_set->first->next->field->name != NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->sel_set->first->next->next->next->field->sel_set->first->next->field->name->token_type == 'a'); + TEST_STRG(doc->first_def->op_def->sel_set->first->field->sel_set->first->next->next->next->field->sel_set->first->next->field->name->token_string, "day"); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->sel_set->first->next->next->next->field->sel_set->first->next->field->args == NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->sel_set->first->next->next->next->field->sel_set->first->next->field->directives == NULL); + TEST_ABRT(doc->first_def->op_def->sel_set->first->field->sel_set->first->next->next->next->field->sel_set->first->next->field->sel_set == NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->sel_set->first->next->next->next->field->sel_set->first->next->next == NULL); + TEST_ABRT(doc->first_def->op_def->sel_set->first->field->sel_set->first->next->next->next->next != NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->sel_set->first->next->next->next->next->frag_spread == NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->sel_set->first->next->next->next->next->inline_frag == NULL); + TEST_ABRT(doc->first_def->op_def->sel_set->first->field->sel_set->first->next->next->next->next->field != NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->sel_set->first->next->next->next->next->field->alias == NULL); + TEST_ABRT(doc->first_def->op_def->sel_set->first->field->sel_set->first->next->next->next->next->field->name != NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->sel_set->first->next->next->next->next->field->name->token_type == 'a'); + TEST_STRG(doc->first_def->op_def->sel_set->first->field->sel_set->first->next->next->next->next->field->name->token_string, "friends"); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->sel_set->first->next->next->next->next->field->args == NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->sel_set->first->next->next->next->next->field->directives == NULL); + TEST_ABRT(doc->first_def->op_def->sel_set->first->field->sel_set->first->next->next->next->next->field->sel_set != NULL); + TEST_ABRT(doc->first_def->op_def->sel_set->first->field->sel_set->first->next->next->next->next->field->sel_set->first != NULL); + TEST_ABRT(doc->first_def->op_def->sel_set->first->field->sel_set->first->next->next->next->next->field->sel_set->first->field != NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->sel_set->first->next->next->next->next->field->sel_set->first->field->alias == NULL); + TEST_ABRT(doc->first_def->op_def->sel_set->first->field->sel_set->first->next->next->next->next->field->sel_set->first->field->name != NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->sel_set->first->next->next->next->next->field->sel_set->first->field->name->token_type == 'a'); + TEST_STRG(doc->first_def->op_def->sel_set->first->field->sel_set->first->next->next->next->next->field->sel_set->first->field->name->token_string, "name"); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->sel_set->first->next->next->next->next->field->sel_set->first->field->args == NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->sel_set->first->next->next->next->next->field->sel_set->first->field->directives == NULL); + TEST_ABRT(doc->first_def->op_def->sel_set->first->field->sel_set->first->next->next->next->next->field->sel_set->first->field->sel_set == NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->sel_set->first->next->next->next->next->field->sel_set->first->next == NULL); + TEST_ABRT(doc->first_def->op_def->sel_set->first->field->sel_set->first->next->next->next->next->next == NULL); + tokens = tal_free(tokens); +} + +void check_example_9(char *source) { + struct list_head *tokens; + struct graphql_token *tok; + + if (!mute) printf("// Example No. 9\n"); + + sprintf(source, "\ +# `me` could represent the currently logged in viewer.\n\ +{\n\ + me {\n\ + name\n\ + }\n\ +}\n\ +\n\ +# `user` represents one of many users in a graph of data, referred to by a\n\ +# unique identifier.\n\ +{\n\ + user(id: 4) {\n\ + name\n\ + }\n\ +}\n\ + "); + + // Test the lexer. + TEST_CONT(graphql_lex(NULL, source, &tokens) == NULL); + TEST_ABRT(listlen(tokens) == 17); + // NOTE: Comments are ignored. + tok = list_pop(tokens, struct graphql_token, node); TEST_CONT(tok->token_type == '{'); + tok = list_pop(tokens, struct graphql_token, node); TEST_CONT(tok->token_type == 'a'); + TEST_STRG(tok->token_string, "me"); + tok = list_pop(tokens, struct graphql_token, node); TEST_CONT(tok->token_type == '{'); + tok = list_pop(tokens, struct graphql_token, node); TEST_CONT(tok->token_type == 'a'); + TEST_STRG(tok->token_string, "name"); + tok = list_pop(tokens, struct graphql_token, node); TEST_CONT(tok->token_type == '}'); + tok = list_pop(tokens, struct graphql_token, node); TEST_CONT(tok->token_type == '}'); + tok = list_pop(tokens, struct graphql_token, node); TEST_CONT(tok->token_type == '{'); + tok = list_pop(tokens, struct graphql_token, node); TEST_CONT(tok->token_type == 'a'); + TEST_STRG(tok->token_string, "user"); + tok = list_pop(tokens, struct graphql_token, node); TEST_CONT(tok->token_type == '('); + tok = list_pop(tokens, struct graphql_token, node); TEST_CONT(tok->token_type == 'a'); + TEST_STRG(tok->token_string, "id"); + tok = list_pop(tokens, struct graphql_token, node); TEST_CONT(tok->token_type == ':'); + tok = list_pop(tokens, struct graphql_token, node); TEST_CONT(tok->token_type == 'i'); + TEST_STRG(tok->token_string, "4"); + tok = list_pop(tokens, struct graphql_token, node); TEST_CONT(tok->token_type == ')'); + tok = list_pop(tokens, struct graphql_token, node); TEST_CONT(tok->token_type == '{'); + tok = list_pop(tokens, struct graphql_token, node); TEST_CONT(tok->token_type == 'a'); + TEST_STRG(tok->token_string, "name"); + tok = list_pop(tokens, struct graphql_token, node); TEST_CONT(tok->token_type == '}'); + tok = list_pop(tokens, struct graphql_token, node); TEST_CONT(tok->token_type == '}'); + tok = list_pop(tokens, struct graphql_token, node); TEST_CONT(tok == NULL); + tokens = tal_free(tokens); + + // Test the parser. + struct graphql_executable_document *doc; + TEST_CONT(graphql_lex(NULL, source, &tokens) == NULL); + TEST_ABRT(graphql_parse(tokens, &doc) == NULL); + TEST_ABRT(doc != NULL); + TEST_ABRT(doc->first_def != NULL); + TEST_ABRT(doc->first_def->next_def != NULL); + TEST_CONT(doc->first_def->frag_def == NULL); + TEST_ABRT(doc->first_def->op_def != NULL); + TEST_CONT(doc->first_def->op_def->op_type == NULL); + TEST_ABRT(doc->first_def->op_def->op_name == NULL); + TEST_CONT(doc->first_def->op_def->vars == NULL); + TEST_CONT(doc->first_def->op_def->directives == NULL); + TEST_ABRT(doc->first_def->op_def->sel_set != NULL); + TEST_ABRT(doc->first_def->op_def->sel_set->first != NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->next == NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->frag_spread == NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->inline_frag == NULL); + TEST_ABRT(doc->first_def->op_def->sel_set->first->field != NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->alias == NULL); + TEST_ABRT(doc->first_def->op_def->sel_set->first->field->name != NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->name->token_type == 'a'); + TEST_STRG(doc->first_def->op_def->sel_set->first->field->name->token_string, "me"); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->args == NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->directives == NULL); + TEST_ABRT(doc->first_def->op_def->sel_set->first->field->sel_set != NULL); + TEST_ABRT(doc->first_def->op_def->sel_set->first->field->sel_set->first != NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->sel_set->first->next == NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->sel_set->first->frag_spread == NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->sel_set->first->inline_frag == NULL); + TEST_ABRT(doc->first_def->op_def->sel_set->first->field->sel_set->first->field != NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->sel_set->first->field->alias == NULL); + TEST_ABRT(doc->first_def->op_def->sel_set->first->field->sel_set->first->field->name != NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->sel_set->first->field->name->token_type == 'a'); + TEST_STRG(doc->first_def->op_def->sel_set->first->field->sel_set->first->field->name->token_string, "name"); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->sel_set->first->field->args == NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->sel_set->first->field->directives == NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->sel_set->first->field->sel_set == NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->sel_set->first->next == NULL); + TEST_CONT(doc->first_def->next_def->next_def == NULL); + TEST_CONT(doc->first_def->next_def->frag_def == NULL); + TEST_ABRT(doc->first_def->next_def->op_def != NULL); + TEST_CONT(doc->first_def->next_def->op_def->op_type == NULL); + TEST_CONT(doc->first_def->next_def->op_def->op_name == NULL); + TEST_CONT(doc->first_def->next_def->op_def->vars == NULL); + TEST_CONT(doc->first_def->next_def->op_def->directives == NULL); + TEST_ABRT(doc->first_def->next_def->op_def->sel_set != NULL); + TEST_ABRT(doc->first_def->next_def->op_def->sel_set->first != NULL); + TEST_CONT(doc->first_def->next_def->op_def->sel_set->first->next == NULL); + TEST_CONT(doc->first_def->next_def->op_def->sel_set->first->frag_spread == NULL); + TEST_CONT(doc->first_def->next_def->op_def->sel_set->first->inline_frag == NULL); + TEST_ABRT(doc->first_def->next_def->op_def->sel_set->first->field != NULL); + TEST_CONT(doc->first_def->next_def->op_def->sel_set->first->field->alias == NULL); + TEST_ABRT(doc->first_def->next_def->op_def->sel_set->first->field->name != NULL); + TEST_CONT(doc->first_def->next_def->op_def->sel_set->first->field->name->token_type == 'a'); + TEST_STRG(doc->first_def->next_def->op_def->sel_set->first->field->name->token_string, "user"); + TEST_ABRT(doc->first_def->next_def->op_def->sel_set->first->field->args != NULL); + TEST_ABRT(doc->first_def->next_def->op_def->sel_set->first->field->args->first != NULL); + TEST_CONT(doc->first_def->next_def->op_def->sel_set->first->field->args->first->next == NULL); + TEST_ABRT(doc->first_def->next_def->op_def->sel_set->first->field->args->first->name != NULL); + TEST_CONT(doc->first_def->next_def->op_def->sel_set->first->field->args->first->name->token_type == 'a'); + TEST_STRG(doc->first_def->next_def->op_def->sel_set->first->field->args->first->name->token_string, "id"); + TEST_ABRT(doc->first_def->next_def->op_def->sel_set->first->field->args->first->val != NULL); + TEST_ABRT(doc->first_def->next_def->op_def->sel_set->first->field->args->first->val->int_val != NULL); + TEST_ABRT(doc->first_def->next_def->op_def->sel_set->first->field->args->first->val->int_val->val != NULL); + TEST_CONT(doc->first_def->next_def->op_def->sel_set->first->field->args->first->val->int_val->val->token_type == 'i'); + TEST_STRG(doc->first_def->next_def->op_def->sel_set->first->field->args->first->val->int_val->val->token_string, "4"); + TEST_CONT(doc->first_def->next_def->op_def->sel_set->first->field->directives == NULL); + TEST_ABRT(doc->first_def->next_def->op_def->sel_set->first->field->sel_set != NULL); + TEST_ABRT(doc->first_def->next_def->op_def->sel_set->first->field->sel_set->first != NULL); + TEST_CONT(doc->first_def->next_def->op_def->sel_set->first->field->sel_set->first->next == NULL); + TEST_CONT(doc->first_def->next_def->op_def->sel_set->first->field->sel_set->first->frag_spread == NULL); + TEST_CONT(doc->first_def->next_def->op_def->sel_set->first->field->sel_set->first->inline_frag == NULL); + TEST_ABRT(doc->first_def->next_def->op_def->sel_set->first->field->sel_set->first->field != NULL); + TEST_CONT(doc->first_def->next_def->op_def->sel_set->first->field->sel_set->first->field->alias == NULL); + TEST_CONT(doc->first_def->next_def->op_def->sel_set->first->field->sel_set->first->field->args == NULL); + TEST_CONT(doc->first_def->next_def->op_def->sel_set->first->field->sel_set->first->field->directives == NULL); + TEST_CONT(doc->first_def->next_def->op_def->sel_set->first->field->sel_set->first->field->sel_set == NULL); + TEST_ABRT(doc->first_def->next_def->op_def->sel_set->first->field->sel_set->first->field->name != NULL); + TEST_CONT(doc->first_def->next_def->op_def->sel_set->first->field->sel_set->first->field->name->token_type == 'a'); + TEST_STRG(doc->first_def->next_def->op_def->sel_set->first->field->sel_set->first->field->name->token_string, "name"); + tokens = tal_free(tokens); +} + +void check_example_10(char *source) { + struct list_head *tokens; + struct graphql_token *tok; + + if (!mute) printf("// Example No. 10\n"); + + sprintf(source, "\ +{\n\ + user(id: 4) {\n\ + id\n\ + name\n\ + profilePic(size: 100)\n\ + }\n\ +}\n\ + "); + + // Test the lexer. + TEST_CONT(graphql_lex(NULL, source, &tokens) == NULL); + TEST_ABRT(listlen(tokens) == 18); + tok = list_pop(tokens, struct graphql_token, node); TEST_CONT(tok->token_type == '{'); + tok = list_pop(tokens, struct graphql_token, node); TEST_CONT(tok->token_type == 'a'); + TEST_STRG(tok->token_string, "user"); + tok = list_pop(tokens, struct graphql_token, node); TEST_CONT(tok->token_type == '('); + tok = list_pop(tokens, struct graphql_token, node); TEST_CONT(tok->token_type == 'a'); + TEST_STRG(tok->token_string, "id"); + tok = list_pop(tokens, struct graphql_token, node); TEST_CONT(tok->token_type == ':'); + tok = list_pop(tokens, struct graphql_token, node); TEST_CONT(tok->token_type == 'i'); + TEST_STRG(tok->token_string, "4"); + tok = list_pop(tokens, struct graphql_token, node); TEST_CONT(tok->token_type == ')'); + tok = list_pop(tokens, struct graphql_token, node); TEST_CONT(tok->token_type == '{'); + tok = list_pop(tokens, struct graphql_token, node); TEST_CONT(tok->token_type == 'a'); + TEST_STRG(tok->token_string, "id"); + tok = list_pop(tokens, struct graphql_token, node); TEST_CONT(tok->token_type == 'a'); + TEST_STRG(tok->token_string, "name"); + tok = list_pop(tokens, struct graphql_token, node); TEST_CONT(tok->token_type == 'a'); + TEST_STRG(tok->token_string, "profilePic"); + tok = list_pop(tokens, struct graphql_token, node); TEST_CONT(tok->token_type == '('); + tok = list_pop(tokens, struct graphql_token, node); TEST_CONT(tok->token_type == 'a'); + TEST_STRG(tok->token_string, "size"); + tok = list_pop(tokens, struct graphql_token, node); TEST_CONT(tok->token_type == ':'); + tok = list_pop(tokens, struct graphql_token, node); TEST_CONT(tok->token_type == 'i'); + TEST_STRG(tok->token_string, "100"); + tok = list_pop(tokens, struct graphql_token, node); TEST_CONT(tok->token_type == ')'); + tok = list_pop(tokens, struct graphql_token, node); TEST_CONT(tok->token_type == '}'); + tok = list_pop(tokens, struct graphql_token, node); TEST_CONT(tok->token_type == '}'); + tok = list_pop(tokens, struct graphql_token, node); TEST_CONT(tok == NULL); + tokens = tal_free(tokens); + + // Test the parser. + struct graphql_executable_document *doc; + TEST_CONT(graphql_lex(NULL, source, &tokens) == NULL); + TEST_ABRT(graphql_parse(tokens, &doc) == NULL); + TEST_ABRT(doc != NULL); + TEST_ABRT(doc->first_def != NULL); + TEST_CONT(doc->first_def->next_def == NULL); + TEST_CONT(doc->first_def->frag_def == NULL); + TEST_ABRT(doc->first_def->op_def != NULL); + TEST_CONT(doc->first_def->op_def->op_type == NULL); + TEST_CONT(doc->first_def->op_def->op_name == NULL); + TEST_CONT(doc->first_def->op_def->vars == NULL); + TEST_CONT(doc->first_def->op_def->directives == NULL); + TEST_ABRT(doc->first_def->op_def->sel_set != NULL); + TEST_ABRT(doc->first_def->op_def->sel_set->first != NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->next == NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->frag_spread == NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->inline_frag == NULL); + TEST_ABRT(doc->first_def->op_def->sel_set->first->field != NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->alias == NULL); + TEST_ABRT(doc->first_def->op_def->sel_set->first->field->name != NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->name->token_type == 'a'); + TEST_STRG(doc->first_def->op_def->sel_set->first->field->name->token_string, "user"); + TEST_ABRT(doc->first_def->op_def->sel_set->first->field->args != NULL); + TEST_ABRT(doc->first_def->op_def->sel_set->first->field->args->first != NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->args->first->next == NULL); + TEST_ABRT(doc->first_def->op_def->sel_set->first->field->args->first->name != NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->args->first->name->token_type == 'a'); + TEST_STRG(doc->first_def->op_def->sel_set->first->field->args->first->name->token_string, "id"); + TEST_ABRT(doc->first_def->op_def->sel_set->first->field->args->first->val != NULL); + TEST_ABRT(doc->first_def->op_def->sel_set->first->field->args->first->val->int_val != NULL); + TEST_ABRT(doc->first_def->op_def->sel_set->first->field->args->first->val->int_val->val != NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->args->first->val->int_val->val->token_type == 'i'); + TEST_STRG(doc->first_def->op_def->sel_set->first->field->args->first->val->int_val->val->token_string, "4"); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->directives == NULL); + TEST_ABRT(doc->first_def->op_def->sel_set->first->field->sel_set != NULL); + TEST_ABRT(doc->first_def->op_def->sel_set->first->field->sel_set->first != NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->sel_set->first->frag_spread == NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->sel_set->first->inline_frag == NULL); + TEST_ABRT(doc->first_def->op_def->sel_set->first->field->sel_set->first->field != NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->sel_set->first->field->alias == NULL); + TEST_ABRT(doc->first_def->op_def->sel_set->first->field->sel_set->first->field->name != NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->sel_set->first->field->name->token_type == 'a'); + TEST_STRG(doc->first_def->op_def->sel_set->first->field->sel_set->first->field->name->token_string, "id"); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->sel_set->first->field->args == NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->sel_set->first->field->directives == NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->sel_set->first->field->sel_set == NULL); + TEST_ABRT(doc->first_def->op_def->sel_set->first->field->sel_set->first->next != NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->sel_set->first->next->frag_spread == NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->sel_set->first->next->inline_frag == NULL); + TEST_ABRT(doc->first_def->op_def->sel_set->first->field->sel_set->first->next->field != NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->sel_set->first->next->field->alias == NULL); + TEST_ABRT(doc->first_def->op_def->sel_set->first->field->sel_set->first->next->field->name != NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->sel_set->first->next->field->name->token_type == 'a'); + TEST_STRG(doc->first_def->op_def->sel_set->first->field->sel_set->first->next->field->name->token_string, "name"); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->sel_set->first->next->field->args == NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->sel_set->first->next->field->directives == NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->sel_set->first->next->field->sel_set == NULL); + TEST_ABRT(doc->first_def->op_def->sel_set->first->field->sel_set->first->next->next != NULL); + TEST_ABRT(doc->first_def->op_def->sel_set->first->field->sel_set->first->next->next->next == NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->sel_set->first->next->next->frag_spread == NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->sel_set->first->next->next->inline_frag == NULL); + TEST_ABRT(doc->first_def->op_def->sel_set->first->field->sel_set->first->next->next->field != NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->sel_set->first->next->next->field->alias == NULL); + TEST_ABRT(doc->first_def->op_def->sel_set->first->field->sel_set->first->next->next->field->name != NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->sel_set->first->next->next->field->name->token_type == 'a'); + TEST_STRG(doc->first_def->op_def->sel_set->first->field->sel_set->first->next->next->field->name->token_string, "profilePic"); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->sel_set->first->next->next->field->directives == NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->sel_set->first->next->next->field->sel_set == NULL); + TEST_ABRT(doc->first_def->op_def->sel_set->first->field->sel_set->first->next->next->field->args != NULL); + TEST_ABRT(doc->first_def->op_def->sel_set->first->field->sel_set->first->next->next->field->args->first != NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->sel_set->first->next->next->field->args->first->next == NULL); + TEST_ABRT(doc->first_def->op_def->sel_set->first->field->sel_set->first->next->next->field->args->first->name != NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->sel_set->first->next->next->field->args->first->name->token_type == 'a'); + TEST_STRG(doc->first_def->op_def->sel_set->first->field->sel_set->first->next->next->field->args->first->name->token_string, "size"); + TEST_ABRT(doc->first_def->op_def->sel_set->first->field->sel_set->first->next->next->field->args->first->val != NULL); + TEST_ABRT(doc->first_def->op_def->sel_set->first->field->sel_set->first->next->next->field->args->first->val->int_val != NULL); + TEST_ABRT(doc->first_def->op_def->sel_set->first->field->sel_set->first->next->next->field->args->first->val->int_val->val != NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->sel_set->first->next->next->field->args->first->val->int_val->val->token_type == 'i'); + TEST_STRG(doc->first_def->op_def->sel_set->first->field->sel_set->first->next->next->field->args->first->val->int_val->val->token_string, "100"); + tokens = tal_free(tokens); +} + +void check_example_11(char *source) { + struct list_head *tokens; + struct graphql_token *tok; + + if (!mute) printf("// Example No. 11\n"); + + sprintf(source, "\ +{\n\ + user(id: 4) {\n\ + id\n\ + name\n\ + profilePic(width: 100, height: 50)\n\ + }\n\ +}\n\ + "); + + // Test the lexer. + TEST_CONT(graphql_lex(NULL, source, &tokens) == NULL); + TEST_ABRT(listlen(tokens) == 21); + tok = list_pop(tokens, struct graphql_token, node); TEST_CONT(tok->token_type == '{'); + tok = list_pop(tokens, struct graphql_token, node); TEST_CONT(tok->token_type == 'a'); + TEST_STRG(tok->token_string, "user"); + tok = list_pop(tokens, struct graphql_token, node); TEST_CONT(tok->token_type == '('); + tok = list_pop(tokens, struct graphql_token, node); TEST_CONT(tok->token_type == 'a'); + TEST_STRG(tok->token_string, "id"); + tok = list_pop(tokens, struct graphql_token, node); TEST_CONT(tok->token_type == ':'); + tok = list_pop(tokens, struct graphql_token, node); TEST_CONT(tok->token_type == 'i'); + TEST_STRG(tok->token_string, "4"); + tok = list_pop(tokens, struct graphql_token, node); TEST_CONT(tok->token_type == ')'); + tok = list_pop(tokens, struct graphql_token, node); TEST_CONT(tok->token_type == '{'); + tok = list_pop(tokens, struct graphql_token, node); TEST_CONT(tok->token_type == 'a'); + TEST_STRG(tok->token_string, "id"); + tok = list_pop(tokens, struct graphql_token, node); TEST_CONT(tok->token_type == 'a'); + TEST_STRG(tok->token_string, "name"); + tok = list_pop(tokens, struct graphql_token, node); TEST_CONT(tok->token_type == 'a'); + TEST_STRG(tok->token_string, "profilePic"); + tok = list_pop(tokens, struct graphql_token, node); TEST_CONT(tok->token_type == '('); + tok = list_pop(tokens, struct graphql_token, node); TEST_CONT(tok->token_type == 'a'); + TEST_STRG(tok->token_string, "width"); + tok = list_pop(tokens, struct graphql_token, node); TEST_CONT(tok->token_type == ':'); + tok = list_pop(tokens, struct graphql_token, node); TEST_CONT(tok->token_type == 'i'); + TEST_STRG(tok->token_string, "100"); + // NOTE: Comma is ignored. + tok = list_pop(tokens, struct graphql_token, node); TEST_CONT(tok->token_type == 'a'); + TEST_STRG(tok->token_string, "height"); + tok = list_pop(tokens, struct graphql_token, node); TEST_CONT(tok->token_type == ':'); + tok = list_pop(tokens, struct graphql_token, node); TEST_CONT(tok->token_type == 'i'); + TEST_STRG(tok->token_string, "50"); + tok = list_pop(tokens, struct graphql_token, node); TEST_CONT(tok->token_type == ')'); + tok = list_pop(tokens, struct graphql_token, node); TEST_CONT(tok->token_type == '}'); + tok = list_pop(tokens, struct graphql_token, node); TEST_CONT(tok->token_type == '}'); + tok = list_pop(tokens, struct graphql_token, node); TEST_CONT(tok == NULL); + tokens = tal_free(tokens); + + // Test the parser. + struct graphql_executable_document *doc; + TEST_CONT(graphql_lex(NULL, source, &tokens) == NULL); + TEST_ABRT(graphql_parse(tokens, &doc) == NULL); + TEST_ABRT(doc != NULL); + TEST_ABRT(doc->first_def != NULL); + TEST_CONT(doc->first_def->next_def == NULL); + TEST_CONT(doc->first_def->frag_def == NULL); + TEST_ABRT(doc->first_def->op_def != NULL); + TEST_CONT(doc->first_def->op_def->op_type == NULL); + TEST_CONT(doc->first_def->op_def->op_name == NULL); + TEST_CONT(doc->first_def->op_def->vars == NULL); + TEST_CONT(doc->first_def->op_def->directives == NULL); + TEST_ABRT(doc->first_def->op_def->sel_set != NULL); + TEST_ABRT(doc->first_def->op_def->sel_set->first != NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->next == NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->frag_spread == NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->inline_frag == NULL); + TEST_ABRT(doc->first_def->op_def->sel_set->first->field != NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->alias == NULL); + TEST_ABRT(doc->first_def->op_def->sel_set->first->field->name != NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->name->token_type == 'a'); + TEST_STRG(doc->first_def->op_def->sel_set->first->field->name->token_string, "user"); + TEST_ABRT(doc->first_def->op_def->sel_set->first->field->args != NULL); + TEST_ABRT(doc->first_def->op_def->sel_set->first->field->args->first != NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->args->first->next == NULL); + TEST_ABRT(doc->first_def->op_def->sel_set->first->field->args->first->name != NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->args->first->name->token_type == 'a'); + TEST_STRG(doc->first_def->op_def->sel_set->first->field->args->first->name->token_string, "id"); + TEST_ABRT(doc->first_def->op_def->sel_set->first->field->args->first->val != NULL); + TEST_ABRT(doc->first_def->op_def->sel_set->first->field->args->first->val->int_val != NULL); + TEST_ABRT(doc->first_def->op_def->sel_set->first->field->args->first->val->int_val->val != NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->args->first->val->int_val->val->token_type == 'i'); + TEST_STRG(doc->first_def->op_def->sel_set->first->field->args->first->val->int_val->val->token_string, "4"); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->directives == NULL); + TEST_ABRT(doc->first_def->op_def->sel_set->first->field->sel_set != NULL); + TEST_ABRT(doc->first_def->op_def->sel_set->first->field->sel_set->first != NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->sel_set->first->frag_spread == NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->sel_set->first->inline_frag == NULL); + TEST_ABRT(doc->first_def->op_def->sel_set->first->field->sel_set->first->field != NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->sel_set->first->field->alias == NULL); + TEST_ABRT(doc->first_def->op_def->sel_set->first->field->sel_set->first->field->name != NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->sel_set->first->field->name->token_type == 'a'); + TEST_STRG(doc->first_def->op_def->sel_set->first->field->sel_set->first->field->name->token_string, "id"); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->sel_set->first->field->args == NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->sel_set->first->field->directives == NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->sel_set->first->field->sel_set == NULL); + TEST_ABRT(doc->first_def->op_def->sel_set->first->field->sel_set->first->next != NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->sel_set->first->next->frag_spread == NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->sel_set->first->next->inline_frag == NULL); + TEST_ABRT(doc->first_def->op_def->sel_set->first->field->sel_set->first->next->field != NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->sel_set->first->next->field->alias == NULL); + TEST_ABRT(doc->first_def->op_def->sel_set->first->field->sel_set->first->next->field->name != NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->sel_set->first->next->field->name->token_type == 'a'); + TEST_STRG(doc->first_def->op_def->sel_set->first->field->sel_set->first->next->field->name->token_string, "name"); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->sel_set->first->next->field->args == NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->sel_set->first->next->field->directives == NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->sel_set->first->next->field->sel_set == NULL); + TEST_ABRT(doc->first_def->op_def->sel_set->first->field->sel_set->first->next->next != NULL); + TEST_ABRT(doc->first_def->op_def->sel_set->first->field->sel_set->first->next->next->next == NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->sel_set->first->next->next->frag_spread == NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->sel_set->first->next->next->inline_frag == NULL); + TEST_ABRT(doc->first_def->op_def->sel_set->first->field->sel_set->first->next->next->field != NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->sel_set->first->next->next->field->alias == NULL); + TEST_ABRT(doc->first_def->op_def->sel_set->first->field->sel_set->first->next->next->field->name != NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->sel_set->first->next->next->field->name->token_type == 'a'); + TEST_STRG(doc->first_def->op_def->sel_set->first->field->sel_set->first->next->next->field->name->token_string, "profilePic"); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->sel_set->first->next->next->field->directives == NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->sel_set->first->next->next->field->sel_set == NULL); + TEST_ABRT(doc->first_def->op_def->sel_set->first->field->sel_set->first->next->next->field->args != NULL); + TEST_ABRT(doc->first_def->op_def->sel_set->first->field->sel_set->first->next->next->field->args->first != NULL); + TEST_ABRT(doc->first_def->op_def->sel_set->first->field->sel_set->first->next->next->field->args->first->name != NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->sel_set->first->next->next->field->args->first->name->token_type == 'a'); + TEST_STRG(doc->first_def->op_def->sel_set->first->field->sel_set->first->next->next->field->args->first->name->token_string, "width"); + TEST_ABRT(doc->first_def->op_def->sel_set->first->field->sel_set->first->next->next->field->args->first->val != NULL); + TEST_ABRT(doc->first_def->op_def->sel_set->first->field->sel_set->first->next->next->field->args->first->val->int_val != NULL); + TEST_ABRT(doc->first_def->op_def->sel_set->first->field->sel_set->first->next->next->field->args->first->val->int_val->val != NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->sel_set->first->next->next->field->args->first->val->int_val->val->token_type == 'i'); + TEST_STRG(doc->first_def->op_def->sel_set->first->field->sel_set->first->next->next->field->args->first->val->int_val->val->token_string, "100"); + TEST_ABRT(doc->first_def->op_def->sel_set->first->field->sel_set->first->next->next->field->args->first->next != NULL); + TEST_ABRT(doc->first_def->op_def->sel_set->first->field->sel_set->first->next->next->field->args->first->next->name != NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->sel_set->first->next->next->field->args->first->next->name->token_type == 'a'); + TEST_STRG(doc->first_def->op_def->sel_set->first->field->sel_set->first->next->next->field->args->first->next->name->token_string, "height"); + TEST_ABRT(doc->first_def->op_def->sel_set->first->field->sel_set->first->next->next->field->args->first->next->val != NULL); + TEST_ABRT(doc->first_def->op_def->sel_set->first->field->sel_set->first->next->next->field->args->first->next->val->int_val != NULL); + TEST_ABRT(doc->first_def->op_def->sel_set->first->field->sel_set->first->next->next->field->args->first->next->val->int_val->val != NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->sel_set->first->next->next->field->args->first->next->val->int_val->val->token_type == 'i'); + TEST_STRG(doc->first_def->op_def->sel_set->first->field->sel_set->first->next->next->field->args->first->next->val->int_val->val->token_string, "50"); + tokens = tal_free(tokens); +} + +void check_example_12_and_13(const char *source) { + struct list_head *tokens; + struct graphql_token *tok; + + if (!mute) printf("// Example Nos. 12 and 13\n"); + + // Test the lexer. + const char *param; + TEST_CONT(graphql_lex(NULL, source, &tokens) == NULL); + TEST_ABRT(listlen(tokens) == 11); + tok = list_pop(tokens, struct graphql_token, node); TEST_CONT(tok->token_type == '{'); + tok = list_pop(tokens, struct graphql_token, node); TEST_CONT(tok->token_type == 'a'); + TEST_STRG(tok->token_string, "picture"); + tok = list_pop(tokens, struct graphql_token, node); TEST_CONT(tok->token_type == '('); + tok = list_pop(tokens, struct graphql_token, node); TEST_CONT(tok->token_type == 'a'); + TEST_ABRT(tok->token_string != NULL && (streq(tok->token_string, "width") || streq(tok->token_string, "height"))); + param = tok->token_string; + tok = list_pop(tokens, struct graphql_token, node); TEST_CONT(tok->token_type == ':'); + tok = list_pop(tokens, struct graphql_token, node); TEST_CONT(tok->token_type == 'i'); + TEST_CONT(tok->token_string != NULL && ((streq(param, "width") && streq(tok->token_string, "200")) || (streq(param, "height") && streq(tok->token_string, "100")))); + tok = list_pop(tokens, struct graphql_token, node); TEST_CONT(tok->token_type == 'a'); + TEST_CONT(tok->token_string != NULL && (streq(tok->token_string, "width") || streq(tok->token_string, "height"))); + param = tok->token_string; + tok = list_pop(tokens, struct graphql_token, node); TEST_CONT(tok->token_type == ':'); + tok = list_pop(tokens, struct graphql_token, node); TEST_CONT(tok->token_type == 'i'); + TEST_CONT(tok->token_string != NULL && ((streq(param, "width") && streq(tok->token_string, "200")) || (streq(param, "height") && streq(tok->token_string, "100")))); + tok = list_pop(tokens, struct graphql_token, node); TEST_CONT(tok->token_type == ')'); + tok = list_pop(tokens, struct graphql_token, node); TEST_CONT(tok->token_type == '}'); + tok = list_pop(tokens, struct graphql_token, node); TEST_CONT(tok == NULL); + tokens = tal_free(tokens); + + // Test the parser. + struct graphql_argument *arg; + struct graphql_executable_document *doc; + TEST_CONT(graphql_lex(NULL, source, &tokens) == NULL); + TEST_ABRT(graphql_parse(tokens, &doc) == NULL); + TEST_ABRT(doc != NULL); + TEST_ABRT(doc->first_def != NULL); + TEST_CONT(doc->first_def->next_def == NULL); + TEST_CONT(doc->first_def->frag_def == NULL); + TEST_ABRT(doc->first_def->op_def != NULL); + TEST_CONT(doc->first_def->op_def->op_type == NULL); + TEST_CONT(doc->first_def->op_def->op_name == NULL); + TEST_CONT(doc->first_def->op_def->vars == NULL); + TEST_CONT(doc->first_def->op_def->directives == NULL); + TEST_ABRT(doc->first_def->op_def->sel_set != NULL); + TEST_ABRT(doc->first_def->op_def->sel_set->first != NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->next == NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->frag_spread == NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->inline_frag == NULL); + TEST_ABRT(doc->first_def->op_def->sel_set->first->field != NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->alias == NULL); + TEST_ABRT(doc->first_def->op_def->sel_set->first->field->name != NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->name->token_type == 'a'); + TEST_STRG(doc->first_def->op_def->sel_set->first->field->name->token_string, "picture"); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->directives == NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->sel_set == NULL); + TEST_ABRT(doc->first_def->op_def->sel_set->first->field->args != NULL); + TEST_ABRT(doc->first_def->op_def->sel_set->first->field->args->first != NULL); + TEST_ABRT(doc->first_def->op_def->sel_set->first->field->args->first->next != NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->args->first->next->next == NULL); + arg = doc->first_def->op_def->sel_set->first->field->args->first; + if (!streq(doc->first_def->op_def->sel_set->first->field->args->first->name->token_string, "width")) arg = arg->next; + TEST_ABRT(arg->name != NULL); + TEST_CONT(arg->name->token_type == 'a'); + TEST_STRG(arg->name->token_string, "width"); + TEST_ABRT(arg->val != NULL); + TEST_ABRT(arg->val->int_val != NULL); + TEST_ABRT(arg->val->int_val->val != NULL); + TEST_CONT(arg->val->int_val->val->token_type == 'i'); + TEST_STRG(arg->val->int_val->val->token_string, "200"); + arg = doc->first_def->op_def->sel_set->first->field->args->first; + if (!streq(doc->first_def->op_def->sel_set->first->field->args->first->name->token_string, "height")) arg = arg->next; + TEST_ABRT(arg->name != NULL); + TEST_CONT(arg->name->token_type == 'a'); + TEST_STRG(arg->name->token_string, "height"); + TEST_ABRT(arg->val != NULL); + TEST_ABRT(arg->val->int_val != NULL); + TEST_ABRT(arg->val->int_val->val != NULL); + TEST_CONT(arg->val->int_val->val->token_type == 'i'); + TEST_STRG(arg->val->int_val->val->token_string, "100"); + tokens = tal_free(tokens); +} + +void check_example_14(char *source) { + struct list_head *tokens; + struct graphql_token *tok; + + if (!mute) printf("// Example No. 14\n"); + + sprintf(source, "\ +{\n\ + user(id: 4) {\n\ + id\n\ + name\n\ + smallPic: profilePic(size: 64)\n\ + bigPic: profilePic(size: 1024)\n\ + }\n\ +}\n\ + "); + + // Test the lexer. + TEST_CONT(graphql_lex(NULL, source, &tokens) == NULL); + TEST_ABRT(listlen(tokens) == 28); + tok = list_pop(tokens, struct graphql_token, node); TEST_CONT(tok->token_type == '{'); + tok = list_pop(tokens, struct graphql_token, node); TEST_CONT(tok->token_type == 'a'); + TEST_STRG(tok->token_string, "user"); + tok = list_pop(tokens, struct graphql_token, node); TEST_CONT(tok->token_type == '('); + tok = list_pop(tokens, struct graphql_token, node); TEST_CONT(tok->token_type == 'a'); + TEST_STRG(tok->token_string, "id"); + tok = list_pop(tokens, struct graphql_token, node); TEST_CONT(tok->token_type == ':'); + tok = list_pop(tokens, struct graphql_token, node); TEST_CONT(tok->token_type == 'i'); + TEST_STRG(tok->token_string, "4"); + tok = list_pop(tokens, struct graphql_token, node); TEST_CONT(tok->token_type == ')'); + tok = list_pop(tokens, struct graphql_token, node); TEST_CONT(tok->token_type == '{'); + tok = list_pop(tokens, struct graphql_token, node); TEST_CONT(tok->token_type == 'a'); + TEST_STRG(tok->token_string, "id"); + tok = list_pop(tokens, struct graphql_token, node); TEST_CONT(tok->token_type == 'a'); + TEST_STRG(tok->token_string, "name"); + tok = list_pop(tokens, struct graphql_token, node); TEST_CONT(tok->token_type == 'a'); + TEST_STRG(tok->token_string, "smallPic"); + tok = list_pop(tokens, struct graphql_token, node); TEST_CONT(tok->token_type == ':'); + tok = list_pop(tokens, struct graphql_token, node); TEST_CONT(tok->token_type == 'a'); + TEST_STRG(tok->token_string, "profilePic"); + tok = list_pop(tokens, struct graphql_token, node); TEST_CONT(tok->token_type == '('); + tok = list_pop(tokens, struct graphql_token, node); TEST_CONT(tok->token_type == 'a'); + TEST_STRG(tok->token_string, "size"); + tok = list_pop(tokens, struct graphql_token, node); TEST_CONT(tok->token_type == ':'); + tok = list_pop(tokens, struct graphql_token, node); TEST_CONT(tok->token_type == 'i'); + TEST_STRG(tok->token_string, "64"); + tok = list_pop(tokens, struct graphql_token, node); TEST_CONT(tok->token_type == ')'); + tok = list_pop(tokens, struct graphql_token, node); TEST_CONT(tok->token_type == 'a'); + TEST_STRG(tok->token_string, "bigPic"); + tok = list_pop(tokens, struct graphql_token, node); TEST_CONT(tok->token_type == ':'); + tok = list_pop(tokens, struct graphql_token, node); TEST_CONT(tok->token_type == 'a'); + TEST_STRG(tok->token_string, "profilePic"); + tok = list_pop(tokens, struct graphql_token, node); TEST_CONT(tok->token_type == '('); + tok = list_pop(tokens, struct graphql_token, node); TEST_CONT(tok->token_type == 'a'); + TEST_STRG(tok->token_string, "size"); + tok = list_pop(tokens, struct graphql_token, node); TEST_CONT(tok->token_type == ':'); + tok = list_pop(tokens, struct graphql_token, node); TEST_CONT(tok->token_type == 'i'); + TEST_STRG(tok->token_string, "1024"); + tok = list_pop(tokens, struct graphql_token, node); TEST_CONT(tok->token_type == ')'); + tok = list_pop(tokens, struct graphql_token, node); TEST_CONT(tok->token_type == '}'); + tok = list_pop(tokens, struct graphql_token, node); TEST_CONT(tok->token_type == '}'); + tok = list_pop(tokens, struct graphql_token, node); TEST_CONT(tok == NULL); + tokens = tal_free(tokens); + + // Test the parser. + struct graphql_executable_document *doc; + TEST_CONT(graphql_lex(NULL, source, &tokens) == NULL); + TEST_ABRT(graphql_parse(tokens, &doc) == NULL); + TEST_ABRT(doc != NULL); + TEST_ABRT(doc->first_def != NULL); + TEST_CONT(doc->first_def->next_def == NULL); + TEST_CONT(doc->first_def->frag_def == NULL); + TEST_ABRT(doc->first_def->op_def != NULL); + TEST_CONT(doc->first_def->op_def->op_type == NULL); + TEST_CONT(doc->first_def->op_def->op_name == NULL); + TEST_CONT(doc->first_def->op_def->vars == NULL); + TEST_CONT(doc->first_def->op_def->directives == NULL); + TEST_ABRT(doc->first_def->op_def->sel_set != NULL); + TEST_ABRT(doc->first_def->op_def->sel_set->first != NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->next == NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->frag_spread == NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->inline_frag == NULL); + TEST_ABRT(doc->first_def->op_def->sel_set->first->field != NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->alias == NULL); + TEST_ABRT(doc->first_def->op_def->sel_set->first->field->name != NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->name->token_type == 'a'); + TEST_STRG(doc->first_def->op_def->sel_set->first->field->name->token_string, "user"); + TEST_ABRT(doc->first_def->op_def->sel_set->first->field->args != NULL); + TEST_ABRT(doc->first_def->op_def->sel_set->first->field->args->first != NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->args->first->next == NULL); + TEST_ABRT(doc->first_def->op_def->sel_set->first->field->args->first->name != NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->args->first->name->token_type == 'a'); + TEST_STRG(doc->first_def->op_def->sel_set->first->field->args->first->name->token_string, "id"); + TEST_ABRT(doc->first_def->op_def->sel_set->first->field->args->first->val != NULL); + TEST_ABRT(doc->first_def->op_def->sel_set->first->field->args->first->val->int_val != NULL); + TEST_ABRT(doc->first_def->op_def->sel_set->first->field->args->first->val->int_val->val != NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->args->first->val->int_val->val->token_type == 'i'); + TEST_STRG(doc->first_def->op_def->sel_set->first->field->args->first->val->int_val->val->token_string, "4"); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->directives == NULL); + TEST_ABRT(doc->first_def->op_def->sel_set->first->field->sel_set != NULL); + TEST_ABRT(doc->first_def->op_def->sel_set->first->field->sel_set->first != NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->sel_set->first->frag_spread == NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->sel_set->first->inline_frag == NULL); + TEST_ABRT(doc->first_def->op_def->sel_set->first->field->sel_set->first->field != NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->sel_set->first->field->alias == NULL); + TEST_ABRT(doc->first_def->op_def->sel_set->first->field->sel_set->first->field->name != NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->sel_set->first->field->name->token_type == 'a'); + TEST_STRG(doc->first_def->op_def->sel_set->first->field->sel_set->first->field->name->token_string, "id"); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->sel_set->first->field->args == NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->sel_set->first->field->directives == NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->sel_set->first->field->sel_set == NULL); + TEST_ABRT(doc->first_def->op_def->sel_set->first->field->sel_set->first->next != NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->sel_set->first->next->frag_spread == NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->sel_set->first->next->inline_frag == NULL); + TEST_ABRT(doc->first_def->op_def->sel_set->first->field->sel_set->first->next->field != NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->sel_set->first->next->field->alias == NULL); + TEST_ABRT(doc->first_def->op_def->sel_set->first->field->sel_set->first->next->field->name != NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->sel_set->first->next->field->name->token_type == 'a'); + TEST_STRG(doc->first_def->op_def->sel_set->first->field->sel_set->first->next->field->name->token_string, "name"); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->sel_set->first->next->field->args == NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->sel_set->first->next->field->directives == NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->sel_set->first->next->field->sel_set == NULL); + TEST_ABRT(doc->first_def->op_def->sel_set->first->field->sel_set->first->next->next != NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->sel_set->first->next->next->frag_spread == NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->sel_set->first->next->next->inline_frag == NULL); + TEST_ABRT(doc->first_def->op_def->sel_set->first->field->sel_set->first->next->next->field != NULL); + TEST_ABRT(doc->first_def->op_def->sel_set->first->field->sel_set->first->next->next->field->alias != NULL); + TEST_ABRT(doc->first_def->op_def->sel_set->first->field->sel_set->first->next->next->field->alias->name != NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->sel_set->first->next->next->field->alias->name->token_type == 'a'); + TEST_STRG(doc->first_def->op_def->sel_set->first->field->sel_set->first->next->next->field->alias->name->token_string, "smallPic"); + TEST_ABRT(doc->first_def->op_def->sel_set->first->field->sel_set->first->next->next->field->name != NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->sel_set->first->next->next->field->name->token_type == 'a'); + TEST_STRG(doc->first_def->op_def->sel_set->first->field->sel_set->first->next->next->field->name->token_string, "profilePic"); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->sel_set->first->next->next->field->directives == NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->sel_set->first->next->next->field->sel_set == NULL); + TEST_ABRT(doc->first_def->op_def->sel_set->first->field->sel_set->first->next->next->field->args != NULL); + TEST_ABRT(doc->first_def->op_def->sel_set->first->field->sel_set->first->next->next->field->args->first != NULL); + TEST_ABRT(doc->first_def->op_def->sel_set->first->field->sel_set->first->next->next->field->args->first->name != NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->sel_set->first->next->next->field->args->first->name->token_type == 'a'); + TEST_STRG(doc->first_def->op_def->sel_set->first->field->sel_set->first->next->next->field->args->first->name->token_string, "size"); + TEST_ABRT(doc->first_def->op_def->sel_set->first->field->sel_set->first->next->next->field->args->first->val != NULL); + TEST_ABRT(doc->first_def->op_def->sel_set->first->field->sel_set->first->next->next->field->args->first->val->int_val != NULL); + TEST_ABRT(doc->first_def->op_def->sel_set->first->field->sel_set->first->next->next->field->args->first->val->int_val->val != NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->sel_set->first->next->next->field->args->first->val->int_val->val->token_type == 'i'); + TEST_STRG(doc->first_def->op_def->sel_set->first->field->sel_set->first->next->next->field->args->first->val->int_val->val->token_string, "64"); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->sel_set->first->next->next->field->args->first->next == NULL); + TEST_ABRT(doc->first_def->op_def->sel_set->first->field->sel_set->first->next->next->next != NULL); + TEST_ABRT(doc->first_def->op_def->sel_set->first->field->sel_set->first->next->next->next->next == NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->sel_set->first->next->next->next->frag_spread == NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->sel_set->first->next->next->next->inline_frag == NULL); + TEST_ABRT(doc->first_def->op_def->sel_set->first->field->sel_set->first->next->next->next->field != NULL); + TEST_ABRT(doc->first_def->op_def->sel_set->first->field->sel_set->first->next->next->next->field->alias != NULL); + TEST_ABRT(doc->first_def->op_def->sel_set->first->field->sel_set->first->next->next->next->field->alias->name != NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->sel_set->first->next->next->next->field->alias->name->token_type == 'a'); + TEST_STRG(doc->first_def->op_def->sel_set->first->field->sel_set->first->next->next->next->field->alias->name->token_string, "bigPic"); + TEST_ABRT(doc->first_def->op_def->sel_set->first->field->sel_set->first->next->next->next->field->name != NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->sel_set->first->next->next->next->field->name->token_type == 'a'); + TEST_STRG(doc->first_def->op_def->sel_set->first->field->sel_set->first->next->next->next->field->name->token_string, "profilePic"); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->sel_set->first->next->next->next->field->directives == NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->sel_set->first->next->next->next->field->sel_set == NULL); + TEST_ABRT(doc->first_def->op_def->sel_set->first->field->sel_set->first->next->next->next->field->args != NULL); + TEST_ABRT(doc->first_def->op_def->sel_set->first->field->sel_set->first->next->next->next->field->args->first != NULL); + TEST_ABRT(doc->first_def->op_def->sel_set->first->field->sel_set->first->next->next->next->field->args->first->name != NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->sel_set->first->next->next->next->field->args->first->name->token_type == 'a'); + TEST_STRG(doc->first_def->op_def->sel_set->first->field->sel_set->first->next->next->next->field->args->first->name->token_string, "size"); + TEST_ABRT(doc->first_def->op_def->sel_set->first->field->sel_set->first->next->next->next->field->args->first->val != NULL); + TEST_ABRT(doc->first_def->op_def->sel_set->first->field->sel_set->first->next->next->next->field->args->first->val->int_val != NULL); + TEST_ABRT(doc->first_def->op_def->sel_set->first->field->sel_set->first->next->next->next->field->args->first->val->int_val->val != NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->sel_set->first->next->next->next->field->args->first->val->int_val->val->token_type == 'i'); + TEST_STRG(doc->first_def->op_def->sel_set->first->field->sel_set->first->next->next->next->field->args->first->val->int_val->val->token_string, "1024"); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->sel_set->first->next->next->next->field->args->first->next == NULL); + tokens = tal_free(tokens); +} + +void check_example_16(char *source) { + struct list_head *tokens; + struct graphql_token *tok; + + if (!mute) printf("// Example No. 16\n"); + + sprintf(source, "\ +{\n\ + zuck: user(id: 4) {\n\ + id\n\ + name\n\ + }\n\ +}\n\ + "); + + // Test the lexer. + TEST_CONT(graphql_lex(NULL, source, &tokens) == NULL); + TEST_ABRT(listlen(tokens) == 14); + tok = list_pop(tokens, struct graphql_token, node); TEST_CONT(tok->token_type == '{'); + tok = list_pop(tokens, struct graphql_token, node); TEST_CONT(tok->token_type == 'a'); + TEST_STRG(tok->token_string, "zuck"); + tok = list_pop(tokens, struct graphql_token, node); TEST_CONT(tok->token_type == ':'); + tok = list_pop(tokens, struct graphql_token, node); TEST_CONT(tok->token_type == 'a'); + TEST_STRG(tok->token_string, "user"); + tok = list_pop(tokens, struct graphql_token, node); TEST_CONT(tok->token_type == '('); + tok = list_pop(tokens, struct graphql_token, node); TEST_CONT(tok->token_type == 'a'); + TEST_STRG(tok->token_string, "id"); + tok = list_pop(tokens, struct graphql_token, node); TEST_CONT(tok->token_type == ':'); + tok = list_pop(tokens, struct graphql_token, node); TEST_CONT(tok->token_type == 'i'); + TEST_STRG(tok->token_string, "4"); + tok = list_pop(tokens, struct graphql_token, node); TEST_CONT(tok->token_type == ')'); + tok = list_pop(tokens, struct graphql_token, node); TEST_CONT(tok->token_type == '{'); + tok = list_pop(tokens, struct graphql_token, node); TEST_CONT(tok->token_type == 'a'); + TEST_STRG(tok->token_string, "id"); + tok = list_pop(tokens, struct graphql_token, node); TEST_CONT(tok->token_type == 'a'); + TEST_STRG(tok->token_string, "name"); + tok = list_pop(tokens, struct graphql_token, node); TEST_CONT(tok->token_type == '}'); + tok = list_pop(tokens, struct graphql_token, node); TEST_CONT(tok->token_type == '}'); + tok = list_pop(tokens, struct graphql_token, node); TEST_CONT(tok == NULL); + tokens = tal_free(tokens); + + // Test the parser. + struct graphql_executable_document *doc; + TEST_CONT(graphql_lex(NULL, source, &tokens) == NULL); + TEST_ABRT(graphql_parse(tokens, &doc) == NULL); + TEST_ABRT(doc != NULL); + TEST_ABRT(doc->first_def != NULL); + TEST_CONT(doc->first_def->next_def == NULL); + TEST_CONT(doc->first_def->frag_def == NULL); + TEST_ABRT(doc->first_def->op_def != NULL); + TEST_CONT(doc->first_def->op_def->op_type == NULL); + TEST_CONT(doc->first_def->op_def->op_name == NULL); + TEST_CONT(doc->first_def->op_def->vars == NULL); + TEST_CONT(doc->first_def->op_def->directives == NULL); + TEST_ABRT(doc->first_def->op_def->sel_set != NULL); + TEST_ABRT(doc->first_def->op_def->sel_set->first != NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->next == NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->frag_spread == NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->inline_frag == NULL); + TEST_ABRT(doc->first_def->op_def->sel_set->first->field != NULL); + TEST_ABRT(doc->first_def->op_def->sel_set->first->field->alias != NULL); + TEST_ABRT(doc->first_def->op_def->sel_set->first->field->alias->name != NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->alias->name->token_type == 'a'); + TEST_STRG(doc->first_def->op_def->sel_set->first->field->alias->name->token_string, "zuck"); + TEST_ABRT(doc->first_def->op_def->sel_set->first->field->name != NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->name->token_type == 'a'); + TEST_STRG(doc->first_def->op_def->sel_set->first->field->name->token_string, "user"); + TEST_ABRT(doc->first_def->op_def->sel_set->first->field->args != NULL); + TEST_ABRT(doc->first_def->op_def->sel_set->first->field->args->first != NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->args->first->next == NULL); + TEST_ABRT(doc->first_def->op_def->sel_set->first->field->args->first->name != NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->args->first->name->token_type == 'a'); + TEST_STRG(doc->first_def->op_def->sel_set->first->field->args->first->name->token_string, "id"); + TEST_ABRT(doc->first_def->op_def->sel_set->first->field->args->first->val != NULL); + TEST_ABRT(doc->first_def->op_def->sel_set->first->field->args->first->val->int_val != NULL); + TEST_ABRT(doc->first_def->op_def->sel_set->first->field->args->first->val->int_val->val != NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->args->first->val->int_val->val->token_type == 'i'); + TEST_STRG(doc->first_def->op_def->sel_set->first->field->args->first->val->int_val->val->token_string, "4"); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->directives == NULL); + TEST_ABRT(doc->first_def->op_def->sel_set->first->field->sel_set != NULL); + TEST_ABRT(doc->first_def->op_def->sel_set->first->field->sel_set->first != NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->sel_set->first->frag_spread == NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->sel_set->first->inline_frag == NULL); + TEST_ABRT(doc->first_def->op_def->sel_set->first->field->sel_set->first->field != NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->sel_set->first->field->alias == NULL); + TEST_ABRT(doc->first_def->op_def->sel_set->first->field->sel_set->first->field->name != NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->sel_set->first->field->name->token_type == 'a'); + TEST_STRG(doc->first_def->op_def->sel_set->first->field->sel_set->first->field->name->token_string, "id"); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->sel_set->first->field->args == NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->sel_set->first->field->directives == NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->sel_set->first->field->sel_set == NULL); + TEST_ABRT(doc->first_def->op_def->sel_set->first->field->sel_set->first->next != NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->sel_set->first->next->frag_spread == NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->sel_set->first->next->inline_frag == NULL); + TEST_ABRT(doc->first_def->op_def->sel_set->first->field->sel_set->first->next->field != NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->sel_set->first->next->field->alias == NULL); + TEST_ABRT(doc->first_def->op_def->sel_set->first->field->sel_set->first->next->field->name != NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->sel_set->first->next->field->name->token_type == 'a'); + TEST_STRG(doc->first_def->op_def->sel_set->first->field->sel_set->first->next->field->name->token_string, "name"); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->sel_set->first->next->field->args == NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->sel_set->first->next->field->directives == NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->sel_set->first->next->field->sel_set == NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->sel_set->first->next->next == NULL); + tokens = tal_free(tokens); +} + +void check_example_18(char *source) { + struct list_head *tokens; + + if (!mute) printf("// Example No. 18\n"); + + sprintf(source, "\ +query noFragments {\n\ + user(id: 4) {\n\ + friends(first: 10) {\n\ + id\n\ + name\n\ + profilePic(size: 50)\n\ + }\n\ + mutualFriends(first: 10) {\n\ + id\n\ + name\n\ + profilePic(size: 50)\n\ + }\n\ + }\n\ +}\n\ + "); + + // Test the lexer. + TEST_CONT(graphql_lex(NULL, source, &tokens) == NULL); + TEST_ABRT(listlen(tokens) == 44); + tokens = tal_free(tokens); + + // Test the parser. + struct graphql_executable_document *doc; + TEST_CONT(graphql_lex(NULL, source, &tokens) == NULL); + TEST_ABRT(graphql_parse(tokens, &doc) == NULL); + TEST_ABRT(doc != NULL); + TEST_ABRT(doc->first_def != NULL); + TEST_ABRT(doc->first_def->op_def != NULL); + TEST_ABRT(doc->first_def->op_def->sel_set != NULL); + TEST_ABRT(doc->first_def->op_def->sel_set->first != NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->next == NULL); + TEST_ABRT(doc->first_def->op_def->sel_set->first->field != NULL); + TEST_ABRT(doc->first_def->op_def->sel_set->first->field->sel_set != NULL); + TEST_ABRT(doc->first_def->op_def->sel_set->first->field->sel_set->first != NULL); + TEST_ABRT(doc->first_def->op_def->sel_set->first->field->sel_set->first->field != NULL); + TEST_ABRT(doc->first_def->op_def->sel_set->first->field->sel_set->first->field->sel_set != NULL); + TEST_ABRT(doc->first_def->op_def->sel_set->first->field->sel_set->first->field->sel_set->first != NULL); + TEST_ABRT(doc->first_def->op_def->sel_set->first->field->sel_set->first->field->sel_set->first->next != NULL); + TEST_ABRT(doc->first_def->op_def->sel_set->first->field->sel_set->first->field->sel_set->first->next->next != NULL); + TEST_ABRT(doc->first_def->op_def->sel_set->first->field->sel_set->first->field->sel_set->first->next->next->next == NULL); + TEST_ABRT(doc->first_def->op_def->sel_set->first->field->sel_set->first->next != NULL); + TEST_ABRT(doc->first_def->op_def->sel_set->first->field->sel_set->first->next->field != NULL); + TEST_ABRT(doc->first_def->op_def->sel_set->first->field->sel_set->first->next->field->sel_set != NULL); + TEST_ABRT(doc->first_def->op_def->sel_set->first->field->sel_set->first->next->field->sel_set->first != NULL); + TEST_ABRT(doc->first_def->op_def->sel_set->first->field->sel_set->first->next->field->sel_set->first->next != NULL); + TEST_ABRT(doc->first_def->op_def->sel_set->first->field->sel_set->first->next->field->sel_set->first->next->next != NULL); + TEST_ABRT(doc->first_def->op_def->sel_set->first->field->sel_set->first->next->field->sel_set->first->next->next->next == NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->sel_set->first->next->next == NULL); + tokens = tal_free(tokens); +} + +void check_example_19(char *source) { + struct list_head *tokens; + struct graphql_token *tok; + + if (!mute) printf("// Example No. 19\n"); + + sprintf(source, "\ +query withFragments {\n\ + user(id: 4) {\n\ + friends(first: 10) {\n\ + ...friendFields\n\ + }\n\ + mutualFriends(first: 10) {\n\ + ...friendFields\n\ + }\n\ + }\n\ +}\n\ +\n\ +fragment friendFields on User {\n\ + id\n\ + name\n\ + profilePic(size: 50)\n\ +}\n\ + "); + + // Test the lexer. + TEST_CONT(graphql_lex(NULL, source, &tokens) == NULL); + TEST_ABRT(listlen(tokens) == 46); + for (int i=0; i<17; i++) + tok = list_pop(tokens, struct graphql_token, node); TEST_CONT(tok->token_type == '{'); + tok = list_pop(tokens, struct graphql_token, node); TEST_CONT(tok->token_type == 0x2026); + tok = list_pop(tokens, struct graphql_token, node); TEST_CONT(tok->token_type == 'a'); + TEST_STRG(tok->token_string, "friendFields"); + tok = list_pop(tokens, struct graphql_token, node); TEST_CONT(tok->token_type == '}'); + for (int i=0; i<7; i++) + tok = list_pop(tokens, struct graphql_token, node); TEST_CONT(tok->token_type == '{'); + tok = list_pop(tokens, struct graphql_token, node); TEST_CONT(tok->token_type == 0x2026); + tok = list_pop(tokens, struct graphql_token, node); TEST_CONT(tok->token_type == 'a'); + TEST_STRG(tok->token_string, "friendFields"); + tok = list_pop(tokens, struct graphql_token, node); TEST_CONT(tok->token_type == '}'); + tokens = tal_free(tokens); + + // Test the parser. + struct graphql_executable_document *doc; + const char *e; + TEST_CONT(graphql_lex(NULL, source, &tokens) == NULL); + TEST_CONT((e = graphql_parse(tokens, &doc)) == NULL); + if (e) printf("%s\n", e); + TEST_ABRT(doc != NULL); + TEST_ABRT(doc->first_def != NULL); + TEST_ABRT(doc->first_def->op_def != NULL); + TEST_CONT(doc->first_def->frag_def == NULL); + TEST_ABRT(doc->first_def->op_def->sel_set != NULL); + TEST_ABRT(doc->first_def->op_def->sel_set->first != NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->next == NULL); + TEST_ABRT(doc->first_def->op_def->sel_set->first->field != NULL); + TEST_ABRT(doc->first_def->op_def->sel_set->first->field->sel_set != NULL); + TEST_ABRT(doc->first_def->op_def->sel_set->first->field->sel_set->first != NULL); + TEST_ABRT(doc->first_def->op_def->sel_set->first->field->sel_set->first->field != NULL); + TEST_ABRT(doc->first_def->op_def->sel_set->first->field->sel_set->first->field->sel_set != NULL); + TEST_ABRT(doc->first_def->op_def->sel_set->first->field->sel_set->first->field->sel_set->first != NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->sel_set->first->field->sel_set->first->next == NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->sel_set->first->field->sel_set->first->field == NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->sel_set->first->field->sel_set->first->inline_frag == NULL); + TEST_ABRT(doc->first_def->op_def->sel_set->first->field->sel_set->first->field->sel_set->first->frag_spread != NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->sel_set->first->field->sel_set->first->frag_spread->directives == NULL); + TEST_ABRT(doc->first_def->op_def->sel_set->first->field->sel_set->first->field->sel_set->first->frag_spread->name != NULL); + TEST_ABRT(doc->first_def->op_def->sel_set->first->field->sel_set->first->field->sel_set->first->frag_spread->name->name != NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->sel_set->first->field->sel_set->first->frag_spread->name->name->token_type == 'a'); + TEST_STRG(doc->first_def->op_def->sel_set->first->field->sel_set->first->field->sel_set->first->frag_spread->name->name->token_string, "friendFields"); + TEST_ABRT(doc->first_def->op_def->sel_set->first->field->sel_set->first->next != NULL); + TEST_ABRT(doc->first_def->op_def->sel_set->first->field->sel_set->first->next->field != NULL); + TEST_ABRT(doc->first_def->op_def->sel_set->first->field->sel_set->first->next->field->sel_set != NULL); + TEST_ABRT(doc->first_def->op_def->sel_set->first->field->sel_set->first->next->field->sel_set->first != NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->sel_set->first->next->field->sel_set->first->next == NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->sel_set->first->next->field->sel_set->first->field == NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->sel_set->first->next->field->sel_set->first->inline_frag == NULL); + TEST_ABRT(doc->first_def->op_def->sel_set->first->field->sel_set->first->next->field->sel_set->first->frag_spread != NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->sel_set->first->next->field->sel_set->first->frag_spread->directives == NULL); + TEST_ABRT(doc->first_def->op_def->sel_set->first->field->sel_set->first->next->field->sel_set->first->frag_spread->name != NULL); + TEST_ABRT(doc->first_def->op_def->sel_set->first->field->sel_set->first->next->field->sel_set->first->frag_spread->name->name != NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->sel_set->first->next->field->sel_set->first->frag_spread->name->name->token_type == 'a'); + TEST_STRG(doc->first_def->op_def->sel_set->first->field->sel_set->first->next->field->sel_set->first->frag_spread->name->name->token_string, "friendFields"); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->sel_set->first->next->next == NULL); + TEST_ABRT(doc->first_def->next_def != NULL); + TEST_CONT(doc->first_def->next_def->next_def == NULL); + TEST_CONT(doc->first_def->next_def->op_def == NULL); + TEST_ABRT(doc->first_def->next_def->frag_def != NULL); + TEST_ABRT(doc->first_def->next_def->frag_def->name != NULL); + TEST_ABRT(doc->first_def->next_def->frag_def->name->name != NULL); + TEST_CONT(doc->first_def->next_def->frag_def->name->name->token_type == 'a'); + TEST_STRG(doc->first_def->next_def->frag_def->name->name->token_string, "friendFields"); + TEST_ABRT(doc->first_def->next_def->frag_def->type_cond != NULL); + TEST_ABRT(doc->first_def->next_def->frag_def->type_cond->named_type != NULL); + TEST_ABRT(doc->first_def->next_def->frag_def->type_cond->named_type->name != NULL); + TEST_CONT(doc->first_def->next_def->frag_def->type_cond->named_type->name->token_type == 'a'); + TEST_STRG(doc->first_def->next_def->frag_def->type_cond->named_type->name->token_string, "User"); + TEST_CONT(doc->first_def->next_def->frag_def->directives == NULL); + TEST_ABRT(doc->first_def->next_def->frag_def->sel_set != NULL); + TEST_ABRT(doc->first_def->next_def->frag_def->sel_set->first != NULL); + TEST_ABRT(doc->first_def->next_def->frag_def->sel_set->first->next != NULL); + TEST_ABRT(doc->first_def->next_def->frag_def->sel_set->first->next->next != NULL); + TEST_ABRT(doc->first_def->next_def->frag_def->sel_set->first->next->next->next == NULL); + tokens = tal_free(tokens); +} + +void check_example_20(char *source) { + struct list_head *tokens; + struct graphql_token *tok; + + if (!mute) printf("// Example No. 20\n"); + + sprintf(source, "\ +query withNestedFragments {\n\ + user(id: 4) {\n\ + friends(first: 10) {\n\ + ...friendFields\n\ + }\n\ + mutualFriends(first: 10) {\n\ + ...friendFields\n\ + }\n\ + }\n\ +}\n\ +\n\ +fragment friendFields on User {\n\ + id\n\ + name\n\ + ...standardProfilePic\n\ +}\n\ +\n\ +fragment standardProfilePic on User {\n\ + profilePic(size: 50)\n\ +}\n\ + "); + + // Test the lexer. + TEST_CONT(graphql_lex(NULL, source, &tokens) == NULL); + TEST_ABRT(listlen(tokens) == 54); + for (int i=0; i<17; i++) + tok = list_pop(tokens, struct graphql_token, node); TEST_CONT(tok->token_type == '{'); + tok = list_pop(tokens, struct graphql_token, node); TEST_CONT(tok->token_type == 0x2026); + tok = list_pop(tokens, struct graphql_token, node); TEST_CONT(tok->token_type == 'a'); + TEST_STRG(tok->token_string, "friendFields"); + tok = list_pop(tokens, struct graphql_token, node); TEST_CONT(tok->token_type == '}'); + for (int i=0; i<7; i++) + tok = list_pop(tokens, struct graphql_token, node); TEST_CONT(tok->token_type == '{'); + tok = list_pop(tokens, struct graphql_token, node); TEST_CONT(tok->token_type == 0x2026); + tok = list_pop(tokens, struct graphql_token, node); TEST_CONT(tok->token_type == 'a'); + TEST_STRG(tok->token_string, "friendFields"); + tok = list_pop(tokens, struct graphql_token, node); TEST_CONT(tok->token_type == '}'); + tokens = tal_free(tokens); + + // Test the parser. + struct graphql_executable_document *doc; + const char *e; + TEST_CONT(graphql_lex(NULL, source, &tokens) == NULL); + TEST_CONT((e = graphql_parse(tokens, &doc)) == NULL); + if (e) printf("%s\n", e); + TEST_ABRT(doc != NULL); + TEST_ABRT(doc->first_def != NULL); + TEST_ABRT(doc->first_def->op_def != NULL); + TEST_CONT(doc->first_def->frag_def == NULL); + TEST_ABRT(doc->first_def->op_def->sel_set != NULL); + TEST_ABRT(doc->first_def->op_def->sel_set->first != NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->next == NULL); + TEST_ABRT(doc->first_def->op_def->sel_set->first->field != NULL); + TEST_ABRT(doc->first_def->op_def->sel_set->first->field->sel_set != NULL); + TEST_ABRT(doc->first_def->op_def->sel_set->first->field->sel_set->first != NULL); + TEST_ABRT(doc->first_def->op_def->sel_set->first->field->sel_set->first->field != NULL); + TEST_ABRT(doc->first_def->op_def->sel_set->first->field->sel_set->first->field->sel_set != NULL); + TEST_ABRT(doc->first_def->op_def->sel_set->first->field->sel_set->first->field->sel_set->first != NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->sel_set->first->field->sel_set->first->next == NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->sel_set->first->field->sel_set->first->field == NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->sel_set->first->field->sel_set->first->inline_frag == NULL); + TEST_ABRT(doc->first_def->op_def->sel_set->first->field->sel_set->first->field->sel_set->first->frag_spread != NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->sel_set->first->field->sel_set->first->frag_spread->directives == NULL); + TEST_ABRT(doc->first_def->op_def->sel_set->first->field->sel_set->first->field->sel_set->first->frag_spread->name != NULL); + TEST_ABRT(doc->first_def->op_def->sel_set->first->field->sel_set->first->field->sel_set->first->frag_spread->name->name != NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->sel_set->first->field->sel_set->first->frag_spread->name->name->token_type == 'a'); + TEST_STRG(doc->first_def->op_def->sel_set->first->field->sel_set->first->field->sel_set->first->frag_spread->name->name->token_string, "friendFields"); + TEST_ABRT(doc->first_def->op_def->sel_set->first->field->sel_set->first->next != NULL); + TEST_ABRT(doc->first_def->op_def->sel_set->first->field->sel_set->first->next->field != NULL); + TEST_ABRT(doc->first_def->op_def->sel_set->first->field->sel_set->first->next->field->sel_set != NULL); + TEST_ABRT(doc->first_def->op_def->sel_set->first->field->sel_set->first->next->field->sel_set->first != NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->sel_set->first->next->field->sel_set->first->next == NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->sel_set->first->next->field->sel_set->first->field == NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->sel_set->first->next->field->sel_set->first->inline_frag == NULL); + TEST_ABRT(doc->first_def->op_def->sel_set->first->field->sel_set->first->next->field->sel_set->first->frag_spread != NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->sel_set->first->next->field->sel_set->first->frag_spread->directives == NULL); + TEST_ABRT(doc->first_def->op_def->sel_set->first->field->sel_set->first->next->field->sel_set->first->frag_spread->name != NULL); + TEST_ABRT(doc->first_def->op_def->sel_set->first->field->sel_set->first->next->field->sel_set->first->frag_spread->name->name != NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->sel_set->first->next->field->sel_set->first->frag_spread->name->name->token_type == 'a'); + TEST_STRG(doc->first_def->op_def->sel_set->first->field->sel_set->first->next->field->sel_set->first->frag_spread->name->name->token_string, "friendFields"); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->sel_set->first->next->next == NULL); + TEST_ABRT(doc->first_def->next_def != NULL); + TEST_CONT(doc->first_def->next_def->op_def == NULL); + TEST_ABRT(doc->first_def->next_def->frag_def != NULL); + TEST_ABRT(doc->first_def->next_def->frag_def->name != NULL); + TEST_ABRT(doc->first_def->next_def->frag_def->name->name != NULL); + TEST_CONT(doc->first_def->next_def->frag_def->name->name->token_type == 'a'); + TEST_STRG(doc->first_def->next_def->frag_def->name->name->token_string, "friendFields"); + TEST_ABRT(doc->first_def->next_def->frag_def->type_cond != NULL); + TEST_ABRT(doc->first_def->next_def->frag_def->type_cond->named_type != NULL); + TEST_ABRT(doc->first_def->next_def->frag_def->type_cond->named_type->name != NULL); + TEST_CONT(doc->first_def->next_def->frag_def->type_cond->named_type->name->token_type == 'a'); + TEST_STRG(doc->first_def->next_def->frag_def->type_cond->named_type->name->token_string, "User"); + TEST_CONT(doc->first_def->next_def->frag_def->directives == NULL); + TEST_ABRT(doc->first_def->next_def->frag_def->sel_set != NULL); + TEST_ABRT(doc->first_def->next_def->frag_def->sel_set->first != NULL); + TEST_ABRT(doc->first_def->next_def->frag_def->sel_set->first->field != NULL); + TEST_CONT(doc->first_def->next_def->frag_def->sel_set->first->frag_spread == NULL); + TEST_CONT(doc->first_def->next_def->frag_def->sel_set->first->inline_frag == NULL); + TEST_ABRT(doc->first_def->next_def->frag_def->sel_set->first->next != NULL); + TEST_ABRT(doc->first_def->next_def->frag_def->sel_set->first->next->field != NULL); + TEST_CONT(doc->first_def->next_def->frag_def->sel_set->first->next->frag_spread == NULL); + TEST_CONT(doc->first_def->next_def->frag_def->sel_set->first->next->inline_frag == NULL); + TEST_ABRT(doc->first_def->next_def->frag_def->sel_set->first->next->next != NULL); + TEST_CONT(doc->first_def->next_def->frag_def->sel_set->first->next->next->field == NULL); + TEST_ABRT(doc->first_def->next_def->frag_def->sel_set->first->next->next->frag_spread != NULL); + TEST_CONT(doc->first_def->next_def->frag_def->sel_set->first->next->next->inline_frag == NULL); + TEST_ABRT(doc->first_def->next_def->frag_def->sel_set->first->next->next->next == NULL); + TEST_ABRT(doc->first_def->next_def->next_def != NULL); + TEST_CONT(doc->first_def->next_def->next_def->next_def == NULL); + TEST_CONT(doc->first_def->next_def->next_def->op_def == NULL); + TEST_ABRT(doc->first_def->next_def->next_def->frag_def != NULL); + TEST_ABRT(doc->first_def->next_def->next_def->frag_def->name != NULL); + TEST_ABRT(doc->first_def->next_def->next_def->frag_def->name->name != NULL); + TEST_CONT(doc->first_def->next_def->next_def->frag_def->name->name->token_type == 'a'); + TEST_STRG(doc->first_def->next_def->next_def->frag_def->name->name->token_string, "standardProfilePic"); + TEST_ABRT(doc->first_def->next_def->next_def->frag_def->type_cond != NULL); + TEST_ABRT(doc->first_def->next_def->next_def->frag_def->type_cond->named_type != NULL); + TEST_ABRT(doc->first_def->next_def->next_def->frag_def->type_cond->named_type->name != NULL); + TEST_CONT(doc->first_def->next_def->next_def->frag_def->type_cond->named_type->name->token_type == 'a'); + TEST_STRG(doc->first_def->next_def->next_def->frag_def->type_cond->named_type->name->token_string, "User"); + TEST_CONT(doc->first_def->next_def->next_def->frag_def->directives == NULL); + TEST_ABRT(doc->first_def->next_def->next_def->frag_def->sel_set != NULL); + TEST_ABRT(doc->first_def->next_def->next_def->frag_def->sel_set->first != NULL); + TEST_CONT(doc->first_def->next_def->next_def->frag_def->sel_set->first->next == NULL); + tokens = tal_free(tokens); +} + +void check_example_21(char *source) { + struct list_head *tokens; + struct graphql_token *tok; + + if (!mute) printf("// Example No. 21\n"); + + sprintf(source, "\ +query FragmentTyping {\n\ + profiles(handles: [\"zuck\", \"coca-cola\"]) {\n\ + handle\n\ + ...userFragment\n\ + ...pageFragment\n\ + }\n\ +}\n\ +\n\ +fragment userFragment on User {\n\ + friends {\n\ + count\n\ + }\n\ +}\n\ +\n\ +fragment pageFragment on Page {\n\ + likers {\n\ + count\n\ + }\n\ +}\n\ + "); + + // Test the lexer. + TEST_CONT(graphql_lex(NULL, source, &tokens) == NULL); + TEST_ABRT(listlen(tokens) == 40); + tok = list_pop(tokens, struct graphql_token, node); TEST_CONT(tok->token_type == 'a'); + TEST_STRG(tok->token_string, "query"); + tok = list_pop(tokens, struct graphql_token, node); TEST_CONT(tok->token_type == 'a'); + TEST_STRG(tok->token_string, "FragmentTyping"); + tok = list_pop(tokens, struct graphql_token, node); TEST_CONT(tok->token_type == '{'); + tok = list_pop(tokens, struct graphql_token, node); TEST_CONT(tok->token_type == 'a'); + TEST_STRG(tok->token_string, "profiles"); + tok = list_pop(tokens, struct graphql_token, node); TEST_CONT(tok->token_type == '('); + tok = list_pop(tokens, struct graphql_token, node); TEST_CONT(tok->token_type == 'a'); + TEST_STRG(tok->token_string, "handles"); + tok = list_pop(tokens, struct graphql_token, node); TEST_CONT(tok->token_type == ':'); + tok = list_pop(tokens, struct graphql_token, node); TEST_CONT(tok->token_type == '['); + tok = list_pop(tokens, struct graphql_token, node); TEST_CONT(tok->token_type == 's'); + TEST_STRG(tok->token_string, "zuck"); + tok = list_pop(tokens, struct graphql_token, node); TEST_CONT(tok->token_type == 's'); + TEST_STRG(tok->token_string, "coca-cola"); + tok = list_pop(tokens, struct graphql_token, node); TEST_CONT(tok->token_type == ']'); + tok = list_pop(tokens, struct graphql_token, node); TEST_CONT(tok->token_type == ')'); + tok = list_pop(tokens, struct graphql_token, node); TEST_CONT(tok->token_type == '{'); + tokens = tal_free(tokens); + + // Test the parser. + struct graphql_executable_document *doc; + const char *e; + TEST_CONT(graphql_lex(NULL, source, &tokens) == NULL); + TEST_CONT((e = graphql_parse(tokens, &doc)) == NULL); + if (e) printf("%s\n", e); + TEST_ABRT(doc != NULL); + TEST_ABRT(doc->first_def != NULL); + TEST_ABRT(doc->first_def->op_def != NULL); + TEST_CONT(doc->first_def->frag_def == NULL); + TEST_ABRT(doc->first_def->op_def->sel_set != NULL); + TEST_ABRT(doc->first_def->op_def->sel_set->first != NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->next == NULL); + TEST_ABRT(doc->first_def->op_def->sel_set->first->field != NULL); + TEST_ABRT(doc->first_def->op_def->sel_set->first->field->sel_set != NULL); + TEST_ABRT(doc->first_def->op_def->sel_set->first->field->sel_set->first != NULL); + TEST_ABRT(doc->first_def->op_def->sel_set->first->field->sel_set->first->field != NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->sel_set->first->field->sel_set == NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->sel_set->first->frag_spread == NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->sel_set->first->inline_frag == NULL); + TEST_ABRT(doc->first_def->op_def->sel_set->first->field->sel_set->first->next != NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->sel_set->first->next->field == NULL); + TEST_ABRT(doc->first_def->op_def->sel_set->first->field->sel_set->first->next->frag_spread != NULL); + TEST_ABRT(doc->first_def->op_def->sel_set->first->field->sel_set->first->next->frag_spread->name != NULL); + TEST_ABRT(doc->first_def->op_def->sel_set->first->field->sel_set->first->next->frag_spread->name->name != NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->sel_set->first->next->frag_spread->name->name->token_type == 'a'); + TEST_STRG(doc->first_def->op_def->sel_set->first->field->sel_set->first->next->frag_spread->name->name->token_string, "userFragment"); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->sel_set->first->next->inline_frag == NULL); + TEST_ABRT(doc->first_def->op_def->sel_set->first->field->sel_set->first->next->next != NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->sel_set->first->next->next->field == NULL); + TEST_ABRT(doc->first_def->op_def->sel_set->first->field->sel_set->first->next->next->frag_spread != NULL); + TEST_ABRT(doc->first_def->op_def->sel_set->first->field->sel_set->first->next->next->frag_spread->name != NULL); + TEST_ABRT(doc->first_def->op_def->sel_set->first->field->sel_set->first->next->next->frag_spread->name->name != NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->sel_set->first->next->next->frag_spread->name->name->token_type == 'a'); + TEST_STRG(doc->first_def->op_def->sel_set->first->field->sel_set->first->next->next->frag_spread->name->name->token_string, "pageFragment"); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->sel_set->first->next->next->inline_frag == NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->sel_set->first->next->next->next == NULL); + TEST_ABRT(doc->first_def->next_def != NULL); + TEST_CONT(doc->first_def->next_def->op_def == NULL); + TEST_ABRT(doc->first_def->next_def->frag_def != NULL); + TEST_ABRT(doc->first_def->next_def->frag_def->name != NULL); + TEST_ABRT(doc->first_def->next_def->frag_def->name->name != NULL); + TEST_CONT(doc->first_def->next_def->frag_def->name->name->token_type == 'a'); + TEST_STRG(doc->first_def->next_def->frag_def->name->name->token_string, "userFragment"); + TEST_ABRT(doc->first_def->next_def->frag_def->type_cond != NULL); + TEST_ABRT(doc->first_def->next_def->frag_def->type_cond->named_type != NULL); + TEST_ABRT(doc->first_def->next_def->frag_def->type_cond->named_type->name != NULL); + TEST_CONT(doc->first_def->next_def->frag_def->type_cond->named_type->name->token_type == 'a'); + TEST_STRG(doc->first_def->next_def->frag_def->type_cond->named_type->name->token_string, "User"); + TEST_CONT(doc->first_def->next_def->frag_def->directives == NULL); + TEST_ABRT(doc->first_def->next_def->frag_def->sel_set != NULL); + TEST_ABRT(doc->first_def->next_def->frag_def->sel_set->first != NULL); + TEST_ABRT(doc->first_def->next_def->frag_def->sel_set->first->field != NULL); + TEST_CONT(doc->first_def->next_def->frag_def->sel_set->first->frag_spread == NULL); + TEST_CONT(doc->first_def->next_def->frag_def->sel_set->first->inline_frag == NULL); + TEST_ABRT(doc->first_def->next_def->frag_def->sel_set->first->next == NULL); + TEST_ABRT(doc->first_def->next_def->next_def != NULL); + TEST_CONT(doc->first_def->next_def->next_def->next_def == NULL); + TEST_CONT(doc->first_def->next_def->next_def->op_def == NULL); + TEST_ABRT(doc->first_def->next_def->next_def->frag_def != NULL); + TEST_ABRT(doc->first_def->next_def->next_def->frag_def->name != NULL); + TEST_ABRT(doc->first_def->next_def->next_def->frag_def->name->name != NULL); + TEST_CONT(doc->first_def->next_def->next_def->frag_def->name->name->token_type == 'a'); + TEST_STRG(doc->first_def->next_def->next_def->frag_def->name->name->token_string, "pageFragment"); + TEST_ABRT(doc->first_def->next_def->next_def->frag_def->type_cond != NULL); + TEST_ABRT(doc->first_def->next_def->next_def->frag_def->type_cond->named_type != NULL); + TEST_ABRT(doc->first_def->next_def->next_def->frag_def->type_cond->named_type->name != NULL); + TEST_CONT(doc->first_def->next_def->next_def->frag_def->type_cond->named_type->name->token_type == 'a'); + TEST_STRG(doc->first_def->next_def->next_def->frag_def->type_cond->named_type->name->token_string, "Page"); + TEST_CONT(doc->first_def->next_def->next_def->frag_def->directives == NULL); + TEST_ABRT(doc->first_def->next_def->next_def->frag_def->sel_set != NULL); + TEST_ABRT(doc->first_def->next_def->next_def->frag_def->sel_set->first != NULL); + TEST_CONT(doc->first_def->next_def->next_def->frag_def->sel_set->first->next == NULL); + tokens = tal_free(tokens); +} + +void check_example_23(char *source) { + struct list_head *tokens; + struct graphql_token *tok; + + if (!mute) printf("// Example No. 23\n"); + + sprintf(source, "\ +query inlineFragmentTyping {\n\ + profiles(handles: [\"zuck\", \"coca-cola\"]) {\n\ + handle\n\ + ... on User {\n\ + friends {\n\ + count\n\ + }\n\ + }\n\ + ... on Page {\n\ + likers {\n\ + count\n\ + }\n\ + }\n\ + }\n\ +}\n\ + "); + + // Test the lexer. + TEST_CONT(graphql_lex(NULL, source, &tokens) == NULL); + TEST_ABRT(listlen(tokens) == 34); + tok = list_pop(tokens, struct graphql_token, node); TEST_CONT(tok->token_type == 'a'); + TEST_STRG(tok->token_string, "query"); + tok = list_pop(tokens, struct graphql_token, node); TEST_CONT(tok->token_type == 'a'); + TEST_STRG(tok->token_string, "inlineFragmentTyping"); + tok = list_pop(tokens, struct graphql_token, node); TEST_CONT(tok->token_type == '{'); + tok = list_pop(tokens, struct graphql_token, node); TEST_CONT(tok->token_type == 'a'); + TEST_STRG(tok->token_string, "profiles"); + tok = list_pop(tokens, struct graphql_token, node); TEST_CONT(tok->token_type == '('); + tok = list_pop(tokens, struct graphql_token, node); TEST_CONT(tok->token_type == 'a'); + TEST_STRG(tok->token_string, "handles"); + tok = list_pop(tokens, struct graphql_token, node); TEST_CONT(tok->token_type == ':'); + tok = list_pop(tokens, struct graphql_token, node); TEST_CONT(tok->token_type == '['); + tok = list_pop(tokens, struct graphql_token, node); TEST_CONT(tok->token_type == 's'); + TEST_STRG(tok->token_string, "zuck"); + tok = list_pop(tokens, struct graphql_token, node); TEST_CONT(tok->token_type == 's'); + TEST_STRG(tok->token_string, "coca-cola"); + tok = list_pop(tokens, struct graphql_token, node); TEST_CONT(tok->token_type == ']'); + tok = list_pop(tokens, struct graphql_token, node); TEST_CONT(tok->token_type == ')'); + tok = list_pop(tokens, struct graphql_token, node); TEST_CONT(tok->token_type == '{'); + tokens = tal_free(tokens); + + // Test the parser. + struct graphql_executable_document *doc; + const char *e; + TEST_CONT(graphql_lex(NULL, source, &tokens) == NULL); + TEST_CONT((e = graphql_parse(tokens, &doc)) == NULL); + if (e) printf("%s\n", e); + TEST_ABRT(doc != NULL); + TEST_ABRT(doc->first_def != NULL); + TEST_ABRT(doc->first_def->op_def != NULL); + TEST_CONT(doc->first_def->frag_def == NULL); + TEST_ABRT(doc->first_def->op_def->sel_set != NULL); + TEST_ABRT(doc->first_def->op_def->sel_set->first != NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->next == NULL); + TEST_ABRT(doc->first_def->op_def->sel_set->first->field != NULL); + TEST_ABRT(doc->first_def->op_def->sel_set->first->field->sel_set != NULL); + TEST_ABRT(doc->first_def->op_def->sel_set->first->field->sel_set->first != NULL); + TEST_ABRT(doc->first_def->op_def->sel_set->first->field->sel_set->first->field != NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->sel_set->first->field->sel_set == NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->sel_set->first->frag_spread == NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->sel_set->first->inline_frag == NULL); + TEST_ABRT(doc->first_def->op_def->sel_set->first->field->sel_set->first->next != NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->sel_set->first->next->field == NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->sel_set->first->next->frag_spread == NULL); + TEST_ABRT(doc->first_def->op_def->sel_set->first->field->sel_set->first->next->inline_frag != NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->sel_set->first->next->inline_frag->directives == NULL); + TEST_ABRT(doc->first_def->op_def->sel_set->first->field->sel_set->first->next->inline_frag->type_cond != NULL); + TEST_ABRT(doc->first_def->op_def->sel_set->first->field->sel_set->first->next->inline_frag->type_cond->named_type->name != NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->sel_set->first->next->inline_frag->type_cond->named_type->name->token_type == 'a'); + TEST_STRG(doc->first_def->op_def->sel_set->first->field->sel_set->first->next->inline_frag->type_cond->named_type->name->token_string, "User"); + TEST_ABRT(doc->first_def->op_def->sel_set->first->field->sel_set->first->next->inline_frag->sel_set != NULL); + TEST_ABRT(doc->first_def->op_def->sel_set->first->field->sel_set->first->next->inline_frag->sel_set->first != NULL); + TEST_ABRT(doc->first_def->op_def->sel_set->first->field->sel_set->first->next->inline_frag->sel_set->first->field != NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->sel_set->first->next->inline_frag->sel_set->first->frag_spread == NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->sel_set->first->next->inline_frag->sel_set->first->inline_frag == NULL); + TEST_ABRT(doc->first_def->op_def->sel_set->first->field->sel_set->first->next->inline_frag->sel_set->first->next == NULL); + TEST_ABRT(doc->first_def->op_def->sel_set->first->field->sel_set->first->next->next != NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->sel_set->first->next->next->field == NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->sel_set->first->next->next->frag_spread == NULL); + TEST_ABRT(doc->first_def->op_def->sel_set->first->field->sel_set->first->next->next->inline_frag != NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->sel_set->first->next->next->inline_frag->directives == NULL); + TEST_ABRT(doc->first_def->op_def->sel_set->first->field->sel_set->first->next->next->inline_frag->type_cond != NULL); + TEST_ABRT(doc->first_def->op_def->sel_set->first->field->sel_set->first->next->next->inline_frag->type_cond->named_type->name != NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->sel_set->first->next->next->inline_frag->type_cond->named_type->name->token_type == 'a'); + TEST_STRG(doc->first_def->op_def->sel_set->first->field->sel_set->first->next->next->inline_frag->type_cond->named_type->name->token_string, "Page"); + TEST_ABRT(doc->first_def->op_def->sel_set->first->field->sel_set->first->next->next->inline_frag->sel_set != NULL); + TEST_ABRT(doc->first_def->op_def->sel_set->first->field->sel_set->first->next->next->inline_frag->sel_set->first != NULL); + TEST_ABRT(doc->first_def->op_def->sel_set->first->field->sel_set->first->next->next->inline_frag->sel_set->first->field != NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->sel_set->first->next->next->inline_frag->sel_set->first->frag_spread == NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->sel_set->first->next->next->inline_frag->sel_set->first->inline_frag == NULL); + TEST_ABRT(doc->first_def->op_def->sel_set->first->field->sel_set->first->next->next->inline_frag->sel_set->first->next == NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->sel_set->first->next->next->next == NULL); + TEST_CONT(doc->first_def->next_def == NULL); + tokens = tal_free(tokens); +} + +void check_example_24(char *source) { + struct list_head *tokens; + struct graphql_token *tok; + + if (!mute) printf("// Example No. 24\n"); + + sprintf(source, "\ +query inlineFragmentNoType($expandedInfo: Boolean) {\n\ + user(handle: \"zuck\") {\n\ + id\n\ + name\n\ + ... @include(if: $expandedInfo) {\n\ + firstName\n\ + lastName\n\ + birthday\n\ + }\n\ + }\n\ +}\n\ + "); + + // Test the lexer. + TEST_CONT(graphql_lex(NULL, source, &tokens) == NULL); + TEST_ABRT(listlen(tokens) == 34); + tok = list_pop(tokens, struct graphql_token, node); TEST_CONT(tok->token_type == 'a'); + TEST_STRG(tok->token_string, "query"); + tok = list_pop(tokens, struct graphql_token, node); TEST_CONT(tok->token_type == 'a'); + TEST_STRG(tok->token_string, "inlineFragmentNoType"); + tok = list_pop(tokens, struct graphql_token, node); TEST_CONT(tok->token_type == '('); + tok = list_pop(tokens, struct graphql_token, node); TEST_CONT(tok->token_type == '$'); + tok = list_pop(tokens, struct graphql_token, node); TEST_CONT(tok->token_type == 'a'); + TEST_STRG(tok->token_string, "expandedInfo"); + tok = list_pop(tokens, struct graphql_token, node); TEST_CONT(tok->token_type == ':'); + tok = list_pop(tokens, struct graphql_token, node); TEST_CONT(tok->token_type == 'a'); + TEST_STRG(tok->token_string, "Boolean"); + tok = list_pop(tokens, struct graphql_token, node); TEST_CONT(tok->token_type == ')'); + tok = list_pop(tokens, struct graphql_token, node); TEST_CONT(tok->token_type == '{'); + tok = list_pop(tokens, struct graphql_token, node); TEST_CONT(tok->token_type == 'a'); + TEST_STRG(tok->token_string, "user"); + tok = list_pop(tokens, struct graphql_token, node); TEST_CONT(tok->token_type == '('); + tok = list_pop(tokens, struct graphql_token, node); TEST_CONT(tok->token_type == 'a'); + TEST_STRG(tok->token_string, "handle"); + tok = list_pop(tokens, struct graphql_token, node); TEST_CONT(tok->token_type == ':'); + tok = list_pop(tokens, struct graphql_token, node); TEST_CONT(tok->token_type == 's'); + TEST_STRG(tok->token_string, "zuck"); + tok = list_pop(tokens, struct graphql_token, node); TEST_CONT(tok->token_type == ')'); + tok = list_pop(tokens, struct graphql_token, node); TEST_CONT(tok->token_type == '{'); + tok = list_pop(tokens, struct graphql_token, node); TEST_CONT(tok->token_type == 'a'); + TEST_STRG(tok->token_string, "id"); + tok = list_pop(tokens, struct graphql_token, node); TEST_CONT(tok->token_type == 'a'); + TEST_STRG(tok->token_string, "name"); + tok = list_pop(tokens, struct graphql_token, node); TEST_CONT(tok->token_type == 0x2026); + tok = list_pop(tokens, struct graphql_token, node); TEST_CONT(tok->token_type == '@'); + tok = list_pop(tokens, struct graphql_token, node); TEST_CONT(tok->token_type == 'a'); + TEST_STRG(tok->token_string, "include"); + tok = list_pop(tokens, struct graphql_token, node); TEST_CONT(tok->token_type == '('); + tok = list_pop(tokens, struct graphql_token, node); TEST_CONT(tok->token_type == 'a'); + TEST_STRG(tok->token_string, "if"); + tok = list_pop(tokens, struct graphql_token, node); TEST_CONT(tok->token_type == ':'); + tok = list_pop(tokens, struct graphql_token, node); TEST_CONT(tok->token_type == '$'); + tok = list_pop(tokens, struct graphql_token, node); TEST_CONT(tok->token_type == 'a'); + TEST_STRG(tok->token_string, "expandedInfo"); + tok = list_pop(tokens, struct graphql_token, node); TEST_CONT(tok->token_type == ')'); + tok = list_pop(tokens, struct graphql_token, node); TEST_CONT(tok->token_type == '{'); + tok = list_pop(tokens, struct graphql_token, node); TEST_CONT(tok->token_type == 'a'); + TEST_STRG(tok->token_string, "firstName"); + tok = list_pop(tokens, struct graphql_token, node); TEST_CONT(tok->token_type == 'a'); + TEST_STRG(tok->token_string, "lastName"); + tok = list_pop(tokens, struct graphql_token, node); TEST_CONT(tok->token_type == 'a'); + TEST_STRG(tok->token_string, "birthday"); + tok = list_pop(tokens, struct graphql_token, node); TEST_CONT(tok->token_type == '}'); + tok = list_pop(tokens, struct graphql_token, node); TEST_CONT(tok->token_type == '}'); + tok = list_pop(tokens, struct graphql_token, node); TEST_CONT(tok->token_type == '}'); + tokens = tal_free(tokens); + + // Test the parser. + struct graphql_executable_document *doc; + TEST_CONT(graphql_lex(NULL, source, &tokens) == NULL); + TEST_CONT(graphql_parse(tokens, &doc) == NULL); + TEST_ABRT(doc != NULL); + TEST_ABRT(doc->first_def != NULL); + TEST_ABRT(doc->first_def->op_def != NULL); + TEST_CONT(doc->first_def->frag_def == NULL); + TEST_ABRT(doc->first_def->op_def->sel_set != NULL); + TEST_ABRT(doc->first_def->op_def->sel_set->first != NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->next == NULL); + TEST_ABRT(doc->first_def->op_def->sel_set->first->field != NULL); + TEST_ABRT(doc->first_def->op_def->sel_set->first->field->sel_set != NULL); + TEST_ABRT(doc->first_def->op_def->sel_set->first->field->sel_set->first != NULL); + TEST_ABRT(doc->first_def->op_def->sel_set->first->field->sel_set->first->field != NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->sel_set->first->field->sel_set == NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->sel_set->first->frag_spread == NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->sel_set->first->inline_frag == NULL); + TEST_ABRT(doc->first_def->op_def->sel_set->first->field->sel_set->first->next != NULL); + TEST_ABRT(doc->first_def->op_def->sel_set->first->field->sel_set->first->next->field != NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->sel_set->first->next->frag_spread == NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->sel_set->first->next->inline_frag == NULL); + TEST_ABRT(doc->first_def->op_def->sel_set->first->field->sel_set->first->next->next != NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->sel_set->first->next->next->field == NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->sel_set->first->next->next->frag_spread == NULL); + TEST_ABRT(doc->first_def->op_def->sel_set->first->field->sel_set->first->next->next->inline_frag != NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->sel_set->first->next->next->inline_frag->type_cond == NULL); + TEST_ABRT(doc->first_def->op_def->sel_set->first->field->sel_set->first->next->next->inline_frag->directives != NULL); + TEST_ABRT(doc->first_def->op_def->sel_set->first->field->sel_set->first->next->next->inline_frag->directives->first != NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->sel_set->first->next->next->inline_frag->directives->first->next == NULL); + TEST_ABRT(doc->first_def->op_def->sel_set->first->field->sel_set->first->next->next->inline_frag->directives->first->name != NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->sel_set->first->next->next->inline_frag->directives->first->name->token_type == 'a'); + TEST_STRG(doc->first_def->op_def->sel_set->first->field->sel_set->first->next->next->inline_frag->directives->first->name->token_string, "include"); + TEST_ABRT(doc->first_def->op_def->sel_set->first->field->sel_set->first->next->next->inline_frag->directives->first->args != NULL); + TEST_ABRT(doc->first_def->op_def->sel_set->first->field->sel_set->first->next->next->inline_frag->directives->first->args->first != NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->sel_set->first->next->next->inline_frag->directives->first->args->first->next == NULL); + TEST_ABRT(doc->first_def->op_def->sel_set->first->field->sel_set->first->next->next->inline_frag->directives->first->args->first->name != NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->sel_set->first->next->next->inline_frag->directives->first->args->first->name->token_type == 'a'); + TEST_STRG(doc->first_def->op_def->sel_set->first->field->sel_set->first->next->next->inline_frag->directives->first->args->first->name->token_string, "if"); + TEST_ABRT(doc->first_def->op_def->sel_set->first->field->sel_set->first->next->next->inline_frag->directives->first->args->first->val != NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->sel_set->first->next->next->inline_frag->directives->first->args->first->val->int_val == NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->sel_set->first->next->next->inline_frag->directives->first->args->first->val->float_val == NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->sel_set->first->next->next->inline_frag->directives->first->args->first->val->bool_val == NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->sel_set->first->next->next->inline_frag->directives->first->args->first->val->str_val == NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->sel_set->first->next->next->inline_frag->directives->first->args->first->val->null_val == NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->sel_set->first->next->next->inline_frag->directives->first->args->first->val->enum_val == NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->sel_set->first->next->next->inline_frag->directives->first->args->first->val->list_val == NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->sel_set->first->next->next->inline_frag->directives->first->args->first->val->obj_val == NULL); + TEST_ABRT(doc->first_def->op_def->sel_set->first->field->sel_set->first->next->next->inline_frag->directives->first->args->first->val->var != NULL); + TEST_ABRT(doc->first_def->op_def->sel_set->first->field->sel_set->first->next->next->inline_frag->directives->first->args->first->val->var->name != NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->sel_set->first->next->next->inline_frag->directives->first->args->first->val->var->name->token_type == 'a'); + TEST_STRG(doc->first_def->op_def->sel_set->first->field->sel_set->first->next->next->inline_frag->directives->first->args->first->val->var->name->token_string, "expandedInfo"); + TEST_ABRT(doc->first_def->op_def->sel_set->first->field->sel_set->first->next->next->inline_frag->sel_set != NULL); + TEST_ABRT(doc->first_def->op_def->sel_set->first->field->sel_set->first->next->next->inline_frag->sel_set->first != NULL); + TEST_ABRT(doc->first_def->op_def->sel_set->first->field->sel_set->first->next->next->inline_frag->sel_set->first->field != NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->sel_set->first->next->next->inline_frag->sel_set->first->frag_spread == NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->sel_set->first->next->next->inline_frag->sel_set->first->inline_frag == NULL); + TEST_ABRT(doc->first_def->op_def->sel_set->first->field->sel_set->first->next->next->inline_frag->sel_set->first->next != NULL); + TEST_ABRT(doc->first_def->op_def->sel_set->first->field->sel_set->first->next->next->inline_frag->sel_set->first->next->next != NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->sel_set->first->next->next->inline_frag->sel_set->first->next->next->next == NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->sel_set->first->next->next->next == NULL); + TEST_CONT(doc->first_def->next_def == NULL); + tokens = tal_free(tokens); +} + +void check_int_value(char *source, int int_value) { + struct list_head *tokens; + struct graphql_token *tok; + + if (!mute) printf("// Int Value Range Check on %d\n", int_value); + + sprintf(source, "\ +{\n\ + user(id: %d) {\n\ + name\n\ + }\n\ +}\n\ + ", int_value); + + char buf[20]; + sprintf(buf, "%d", int_value); + + // Test the lexer. + TEST_CONT(graphql_lex(NULL, source, &tokens) == NULL); + TEST_ABRT(listlen(tokens) == 11); + tok = list_pop(tokens, struct graphql_token, node); TEST_CONT(tok->token_type == '{'); + tok = list_pop(tokens, struct graphql_token, node); TEST_CONT(tok->token_type == 'a'); + tok = list_pop(tokens, struct graphql_token, node); TEST_CONT(tok->token_type == '('); + tok = list_pop(tokens, struct graphql_token, node); TEST_CONT(tok->token_type == 'a'); + tok = list_pop(tokens, struct graphql_token, node); TEST_CONT(tok->token_type == ':'); + tok = list_pop(tokens, struct graphql_token, node); TEST_CONT(tok->token_type == 'i'); + TEST_STRG(tok->token_string, buf); + TEST_CONT(tok->source_line == 2); + TEST_CONT(tok->source_column == 12); + TEST_CONT(tok->source_len == strlen(buf)); + tok = list_pop(tokens, struct graphql_token, node); TEST_CONT(tok->token_type == ')'); + TEST_CONT(tok->source_line == 2); + TEST_CONT(tok->source_column == 12 + strlen(buf)); + TEST_CONT(tok->source_len == 1); + tok = list_pop(tokens, struct graphql_token, node); TEST_CONT(tok->token_type == '{'); + tok = list_pop(tokens, struct graphql_token, node); TEST_CONT(tok->token_type == 'a'); + tok = list_pop(tokens, struct graphql_token, node); TEST_CONT(tok->token_type == '}'); + tok = list_pop(tokens, struct graphql_token, node); TEST_CONT(tok->token_type == '}'); + tok = list_pop(tokens, struct graphql_token, node); TEST_CONT(tok == NULL); + tokens = tal_free(tokens); + + // Test the parser. + struct graphql_executable_document *doc; + TEST_CONT(graphql_lex(NULL, source, &tokens) == NULL); + TEST_CONT(graphql_parse(tokens, &doc) == NULL); + TEST_ABRT(doc != NULL); + TEST_ABRT(doc->first_def != NULL); + TEST_ABRT(doc->first_def->op_def != NULL); + TEST_CONT(doc->first_def->next_def == NULL); + TEST_CONT(doc->first_def->frag_def == NULL); + TEST_ABRT(doc->first_def->op_def->sel_set != NULL); + TEST_ABRT(doc->first_def->op_def->sel_set->first != NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->next == NULL); + TEST_ABRT(doc->first_def->op_def->sel_set->first->field != NULL); + TEST_ABRT(doc->first_def->op_def->sel_set->first->field->args != NULL); + TEST_ABRT(doc->first_def->op_def->sel_set->first->field->args->first != NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->args->first->next == NULL); + TEST_ABRT(doc->first_def->op_def->sel_set->first->field->args->first->val != NULL); + TEST_ABRT(doc->first_def->op_def->sel_set->first->field->args->first->val->int_val != NULL); + TEST_ABRT(doc->first_def->op_def->sel_set->first->field->args->first->val->int_val->val != NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->args->first->val->int_val->val->token_type == 'i'); + TEST_STRG(doc->first_def->op_def->sel_set->first->field->args->first->val->int_val->val->token_string, buf); + TEST_ABRT(doc->first_def->op_def->sel_set->first->field->sel_set != NULL); + TEST_ABRT(doc->first_def->op_def->sel_set->first->field->sel_set->first != NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->sel_set->first->next == NULL); + TEST_ABRT(doc->first_def->op_def->sel_set->first->field->sel_set->first->field != NULL); + tokens = tal_free(tokens); +} + +void check_invalid_int_values(char *source) { + struct list_head *tokens; + + if (!mute) printf("// Invalid Int Values\n"); + + const char *bad_values[] = {"00", "-00", "+1", "1.", "1a", "1e", "0x123", "123L", 0}; + + for (int i=0; bad_values[i]; i++) { + sprintf(source, "\ +{\n\ + user(id: %s) {\n\ + name\n\ + }\n\ +}\n\ + ", bad_values[i]); + + // Test the lexer. + TEST_CONT(graphql_lex(NULL, source, &tokens) != NULL); + TEST_ABRT(listlen(tokens) == 5); + tokens = tal_free(tokens); + + // No need to test parser when lexer fails. + } +} + +void check_float_value(char *source, float float_value, const char *format) { + struct list_head *tokens; + struct graphql_token *tok; + + if (!mute) printf("// Float Value Range Check on %f\n", float_value); + + char buf[100]; + sprintf(buf, "\ +{\n\ + user(id: %s) {\n\ + name\n\ + }\n\ +}\n\ + ", format); + sprintf(source, buf, float_value); + sprintf(buf, format, float_value); + + // Test the lexer. + TEST_CONT(graphql_lex(NULL, source, &tokens) == NULL); + TEST_ABRT(listlen(tokens) == 11); + tok = list_pop(tokens, struct graphql_token, node); TEST_CONT(tok->token_type == '{'); + tok = list_pop(tokens, struct graphql_token, node); TEST_CONT(tok->token_type == 'a'); + tok = list_pop(tokens, struct graphql_token, node); TEST_CONT(tok->token_type == '('); + tok = list_pop(tokens, struct graphql_token, node); TEST_CONT(tok->token_type == 'a'); + tok = list_pop(tokens, struct graphql_token, node); TEST_CONT(tok->token_type == ':'); + tok = list_pop(tokens, struct graphql_token, node); TEST_CONT(tok->token_type == 'f'); + TEST_STRG(tok->token_string, buf); + TEST_CONT(tok->source_line == 2); + TEST_CONT(tok->source_column == 12); + TEST_CONT(tok->source_len == strlen(buf)); + tok = list_pop(tokens, struct graphql_token, node); TEST_CONT(tok->token_type == ')'); + TEST_CONT(tok->source_line == 2); + TEST_CONT(tok->source_column == 12 + strlen(buf)); + TEST_CONT(tok->source_len == 1); + tok = list_pop(tokens, struct graphql_token, node); TEST_CONT(tok->token_type == '{'); + tok = list_pop(tokens, struct graphql_token, node); TEST_CONT(tok->token_type == 'a'); + tok = list_pop(tokens, struct graphql_token, node); TEST_CONT(tok->token_type == '}'); + tok = list_pop(tokens, struct graphql_token, node); TEST_CONT(tok->token_type == '}'); + tok = list_pop(tokens, struct graphql_token, node); TEST_CONT(tok == NULL); + tokens = tal_free(tokens); + + // Test the parser. + struct graphql_executable_document *doc; + TEST_CONT(graphql_lex(NULL, source, &tokens) == NULL); + TEST_CONT(graphql_parse(tokens, &doc) == NULL); + TEST_ABRT(doc != NULL); + TEST_ABRT(doc->first_def != NULL); + TEST_ABRT(doc->first_def->op_def != NULL); + TEST_CONT(doc->first_def->next_def == NULL); + TEST_CONT(doc->first_def->frag_def == NULL); + TEST_ABRT(doc->first_def->op_def->sel_set != NULL); + TEST_ABRT(doc->first_def->op_def->sel_set->first != NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->next == NULL); + TEST_ABRT(doc->first_def->op_def->sel_set->first->field != NULL); + TEST_ABRT(doc->first_def->op_def->sel_set->first->field->args != NULL); + TEST_ABRT(doc->first_def->op_def->sel_set->first->field->args->first != NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->args->first->next == NULL); + TEST_ABRT(doc->first_def->op_def->sel_set->first->field->args->first->val != NULL); + TEST_ABRT(doc->first_def->op_def->sel_set->first->field->args->first->val->float_val != NULL); + TEST_ABRT(doc->first_def->op_def->sel_set->first->field->args->first->val->float_val->val != NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->args->first->val->float_val->val->token_type == 'f'); + TEST_STRG(doc->first_def->op_def->sel_set->first->field->args->first->val->float_val->val->token_string, buf); + TEST_ABRT(doc->first_def->op_def->sel_set->first->field->sel_set != NULL); + TEST_ABRT(doc->first_def->op_def->sel_set->first->field->sel_set->first != NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->sel_set->first->next == NULL); + TEST_ABRT(doc->first_def->op_def->sel_set->first->field->sel_set->first->field != NULL); + tokens = tal_free(tokens); +} + +void check_valid_float_values(char *source) { + struct list_head *tokens; + struct graphql_token *tok; + + if (!mute) printf("// Valid Float Values\n"); + + const char *good_values[] = {"1.0", "1e50", "6.0221413e23", "1.23", 0}; + + for (int i=0; good_values[i]; i++) { + sprintf(source, "\ +{\n\ + user(id: %s) {\n\ + name\n\ + }\n\ +}\n\ + ", good_values[i]); + + // Test the lexer. + TEST_CONT(graphql_lex(NULL, source, &tokens) == NULL); + TEST_ABRT(listlen(tokens) == 11); + tok = list_pop(tokens, struct graphql_token, node); TEST_CONT(tok->token_type == '{'); + tok = list_pop(tokens, struct graphql_token, node); TEST_CONT(tok->token_type == 'a'); + tok = list_pop(tokens, struct graphql_token, node); TEST_CONT(tok->token_type == '('); + tok = list_pop(tokens, struct graphql_token, node); TEST_CONT(tok->token_type == 'a'); + tok = list_pop(tokens, struct graphql_token, node); TEST_CONT(tok->token_type == ':'); + tok = list_pop(tokens, struct graphql_token, node); TEST_CONT(tok->token_type == 'f'); + TEST_STRG(tok->token_string, good_values[i]); + TEST_CONT(tok->source_line == 2); + TEST_CONT(tok->source_column == 12); + TEST_CONT(tok->source_len == strlen(good_values[i])); + tok = list_pop(tokens, struct graphql_token, node); TEST_CONT(tok->token_type == ')'); + TEST_CONT(tok->source_line == 2); + TEST_CONT(tok->source_column == 12 + strlen(good_values[i])); + TEST_CONT(tok->source_len == 1); + tok = list_pop(tokens, struct graphql_token, node); TEST_CONT(tok->token_type == '{'); + tok = list_pop(tokens, struct graphql_token, node); TEST_CONT(tok->token_type == 'a'); + tok = list_pop(tokens, struct graphql_token, node); TEST_CONT(tok->token_type == '}'); + tok = list_pop(tokens, struct graphql_token, node); TEST_CONT(tok->token_type == '}'); + tok = list_pop(tokens, struct graphql_token, node); TEST_CONT(tok == NULL); + tokens = tal_free(tokens); + + // Test the parser. + struct graphql_executable_document *doc; + TEST_CONT(graphql_lex(NULL, source, &tokens) == NULL); + TEST_CONT(graphql_parse(tokens, &doc) == NULL); + TEST_ABRT(doc != NULL); + TEST_ABRT(doc->first_def != NULL); + TEST_ABRT(doc->first_def->op_def != NULL); + TEST_CONT(doc->first_def->next_def == NULL); + TEST_CONT(doc->first_def->frag_def == NULL); + TEST_ABRT(doc->first_def->op_def->sel_set != NULL); + TEST_ABRT(doc->first_def->op_def->sel_set->first != NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->next == NULL); + TEST_ABRT(doc->first_def->op_def->sel_set->first->field != NULL); + TEST_ABRT(doc->first_def->op_def->sel_set->first->field->args != NULL); + TEST_ABRT(doc->first_def->op_def->sel_set->first->field->args->first != NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->args->first->next == NULL); + TEST_ABRT(doc->first_def->op_def->sel_set->first->field->args->first->val != NULL); + TEST_ABRT(doc->first_def->op_def->sel_set->first->field->args->first->val->float_val != NULL); + TEST_ABRT(doc->first_def->op_def->sel_set->first->field->args->first->val->float_val->val != NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->args->first->val->float_val->val->token_type == 'f'); + TEST_STRG(doc->first_def->op_def->sel_set->first->field->args->first->val->float_val->val->token_string, good_values[i]); + TEST_ABRT(doc->first_def->op_def->sel_set->first->field->sel_set != NULL); + TEST_ABRT(doc->first_def->op_def->sel_set->first->field->sel_set->first != NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->sel_set->first->next == NULL); + TEST_ABRT(doc->first_def->op_def->sel_set->first->field->sel_set->first->field != NULL); + tokens = tal_free(tokens); + } +} + +void check_invalid_float_values(char *source) { + struct list_head *tokens; + + if (!mute) printf("// Invalid Float Values\n"); + + const char *bad_values[] = {"00.0", "-00.0", "00e1", "1.23.4", "0x1.2p3", 0}; + + for (int i=0; bad_values[i]; i++) { + sprintf(source, "\ +{\n\ + user(id: %s) {\n\ + name\n\ + }\n\ +}\n\ + ", bad_values[i]); + + // Test the lexer. + TEST_CONT(graphql_lex(NULL, source, &tokens) != NULL); + TEST_ABRT(listlen(tokens) == 5); + tokens = tal_free(tokens); + + // No need to test parser when lexer fails. + } +} + +void check_boolean_values(char *source) { + struct list_head *tokens; + + if (!mute) printf("// Boolean Values\n"); + + const char *good_values[] = {"true", "false", 0}; + + for (int i=0; good_values[i]; i++) { + sprintf(source, "\ +{\n\ + user(id: %s) {\n\ + name\n\ + }\n\ +}\n\ + ", good_values[i]); + + // Test the lexer. + TEST_CONT(graphql_lex(NULL, source, &tokens) == NULL); + TEST_ABRT(listlen(tokens) == 11); + tokens = tal_free(tokens); + + // Test the parser. + struct graphql_executable_document *doc; + TEST_CONT(graphql_lex(NULL, source, &tokens) == NULL); + TEST_CONT(graphql_parse(tokens, &doc) == NULL); + TEST_ABRT(doc != NULL); + TEST_ABRT(doc->first_def != NULL); + TEST_ABRT(doc->first_def->op_def != NULL); + TEST_CONT(doc->first_def->next_def == NULL); + TEST_CONT(doc->first_def->frag_def == NULL); + TEST_ABRT(doc->first_def->op_def->sel_set != NULL); + TEST_ABRT(doc->first_def->op_def->sel_set->first != NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->next == NULL); + TEST_ABRT(doc->first_def->op_def->sel_set->first->field != NULL); + TEST_ABRT(doc->first_def->op_def->sel_set->first->field->args != NULL); + TEST_ABRT(doc->first_def->op_def->sel_set->first->field->args->first != NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->args->first->next == NULL); + TEST_ABRT(doc->first_def->op_def->sel_set->first->field->args->first->val != NULL); + TEST_ABRT(doc->first_def->op_def->sel_set->first->field->args->first->val->bool_val != NULL); + TEST_ABRT(doc->first_def->op_def->sel_set->first->field->args->first->val->bool_val->val != NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->args->first->val->bool_val->val->token_type == 'a'); + TEST_STRG(doc->first_def->op_def->sel_set->first->field->args->first->val->bool_val->val->token_string, good_values[i]); + TEST_ABRT(doc->first_def->op_def->sel_set->first->field->sel_set != NULL); + TEST_ABRT(doc->first_def->op_def->sel_set->first->field->sel_set->first != NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->sel_set->first->next == NULL); + TEST_ABRT(doc->first_def->op_def->sel_set->first->field->sel_set->first->field != NULL); + tokens = tal_free(tokens); + } + + const char *bad_values[] = {"True", "False", "TRUE", "FALSE", 0}; + + for (int i=0; bad_values[i]; i++) { + sprintf(source, "\ +{\n\ + user(id: %s) {\n\ + name\n\ + }\n\ +}\n\ + ", bad_values[i]); + + // Test the lexer. + TEST_CONT(graphql_lex(NULL, source, &tokens) == NULL); + TEST_ABRT(listlen(tokens) == 11); + tokens = tal_free(tokens); + + // Test the parser (it will succeed in parsing the bad values as enum values, not boolean values). + struct graphql_executable_document *doc; + TEST_CONT(graphql_lex(NULL, source, &tokens) == NULL); + TEST_CONT(graphql_parse(tokens, &doc) == NULL); + TEST_ABRT(doc != NULL); + TEST_ABRT(doc->first_def != NULL); + TEST_ABRT(doc->first_def->op_def != NULL); + TEST_CONT(doc->first_def->next_def == NULL); + TEST_CONT(doc->first_def->frag_def == NULL); + TEST_ABRT(doc->first_def->op_def->sel_set != NULL); + TEST_ABRT(doc->first_def->op_def->sel_set->first != NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->next == NULL); + TEST_ABRT(doc->first_def->op_def->sel_set->first->field != NULL); + TEST_ABRT(doc->first_def->op_def->sel_set->first->field->args != NULL); + TEST_ABRT(doc->first_def->op_def->sel_set->first->field->args->first != NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->args->first->next == NULL); + TEST_ABRT(doc->first_def->op_def->sel_set->first->field->args->first->val != NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->args->first->val->var == NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->args->first->val->int_val == NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->args->first->val->float_val == NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->args->first->val->bool_val == NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->args->first->val->str_val == NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->args->first->val->null_val == NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->args->first->val->list_val == NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->args->first->val->obj_val == NULL); + TEST_ABRT(doc->first_def->op_def->sel_set->first->field->args->first->val->enum_val != NULL); + TEST_ABRT(doc->first_def->op_def->sel_set->first->field->args->first->val->enum_val->val != NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->args->first->val->enum_val->val->token_type == 'a'); + TEST_STRG(doc->first_def->op_def->sel_set->first->field->args->first->val->enum_val->val->token_string, bad_values[i]); + TEST_ABRT(doc->first_def->op_def->sel_set->first->field->sel_set != NULL); + TEST_ABRT(doc->first_def->op_def->sel_set->first->field->sel_set->first != NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->sel_set->first->next == NULL); + TEST_ABRT(doc->first_def->op_def->sel_set->first->field->sel_set->first->field != NULL); + tokens = tal_free(tokens); + } +} + +void check_string_value(char *source, const char *test_value, const char *expected_result) { + struct list_head *tokens; + struct graphql_token *tok; + + if (!mute) printf("// String Value Test: %s\n", test_value); + + sprintf(source, "\ +{\n\ + user(id:%s) {\n\ + name\n\ + }\n\ +}\n\ + ", test_value); + + bool block = (test_value[0]=='\"' && test_value[1]=='\"' && test_value[2]=='\"')? true: false; + if (expected_result) { + TEST_CONT(graphql_lex(NULL, source, &tokens) == NULL); + TEST_ABRT(listlen(tokens) == 11); + tok = list_pop(tokens, struct graphql_token, node); TEST_CONT(tok->token_type == '{'); + tok = list_pop(tokens, struct graphql_token, node); TEST_CONT(tok->token_type == 'a'); + tok = list_pop(tokens, struct graphql_token, node); TEST_CONT(tok->token_type == '('); + tok = list_pop(tokens, struct graphql_token, node); TEST_CONT(tok->token_type == 'a'); + tok = list_pop(tokens, struct graphql_token, node); TEST_CONT(tok->token_type == ':'); + tok = list_pop(tokens, struct graphql_token, node); TEST_CONT(tok->token_type == 's'); + TEST_STRG(tok->token_string, expected_result); + TEST_CONT(tok->source_line == 2); + TEST_CONT(tok->source_column == 11 + (block? 3: 1)); + TEST_CONT(tok->source_len == strlen(test_value) - (block? 6: 2)); + tok = list_pop(tokens, struct graphql_token, node); TEST_CONT(tok->token_type == ')'); + TEST_CONT(tok->source_line == 2); + TEST_CONT(tok->source_column == 11 + strlen(test_value)); + TEST_CONT(tok->source_len == 1); + tok = list_pop(tokens, struct graphql_token, node); TEST_CONT(tok->token_type == '{'); + tok = list_pop(tokens, struct graphql_token, node); TEST_CONT(tok->token_type == 'a'); + tok = list_pop(tokens, struct graphql_token, node); TEST_CONT(tok->token_type == '}'); + tok = list_pop(tokens, struct graphql_token, node); TEST_CONT(tok->token_type == '}'); + tok = list_pop(tokens, struct graphql_token, node); TEST_CONT(tok == NULL); + tokens = tal_free(tokens); + + // Test the parser (it will succeed in parsing the bad values as enum values, not boolean values). + struct graphql_executable_document *doc; + TEST_CONT(graphql_lex(NULL, source, &tokens) == NULL); + TEST_CONT(graphql_parse(tokens, &doc) == NULL); + TEST_ABRT(doc != NULL); + TEST_ABRT(doc->first_def != NULL); + TEST_ABRT(doc->first_def->op_def != NULL); + TEST_CONT(doc->first_def->next_def == NULL); + TEST_CONT(doc->first_def->frag_def == NULL); + TEST_ABRT(doc->first_def->op_def->sel_set != NULL); + TEST_ABRT(doc->first_def->op_def->sel_set->first != NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->next == NULL); + TEST_ABRT(doc->first_def->op_def->sel_set->first->field != NULL); + TEST_ABRT(doc->first_def->op_def->sel_set->first->field->args != NULL); + TEST_ABRT(doc->first_def->op_def->sel_set->first->field->args->first != NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->args->first->next == NULL); + TEST_ABRT(doc->first_def->op_def->sel_set->first->field->args->first->val != NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->args->first->val->var == NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->args->first->val->int_val == NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->args->first->val->float_val == NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->args->first->val->bool_val == NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->args->first->val->enum_val == NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->args->first->val->null_val == NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->args->first->val->list_val == NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->args->first->val->obj_val == NULL); + TEST_ABRT(doc->first_def->op_def->sel_set->first->field->args->first->val->str_val != NULL); + TEST_ABRT(doc->first_def->op_def->sel_set->first->field->args->first->val->str_val->val != NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->args->first->val->str_val->val->token_type == 's'); + TEST_STRG(doc->first_def->op_def->sel_set->first->field->args->first->val->str_val->val->token_string, expected_result); + TEST_ABRT(doc->first_def->op_def->sel_set->first->field->sel_set != NULL); + TEST_ABRT(doc->first_def->op_def->sel_set->first->field->sel_set->first != NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->sel_set->first->next == NULL); + TEST_ABRT(doc->first_def->op_def->sel_set->first->field->sel_set->first->field != NULL); + tokens = tal_free(tokens); + } else { + TEST_CONT(graphql_lex(NULL, source, &tokens) != NULL); + tokens = tal_free(tokens); + } +} + +void check_example_25_and_26(const char *source) { + struct list_head *tokens; + struct graphql_token *tok; + + if (!mute) printf("// Example No. 25 and 26\n"); + + TEST_CONT(graphql_lex(NULL, source, &tokens) == NULL); + while ((tok = list_pop(tokens, struct graphql_token, node)) && tok->token_type != 's') { } + if (tok) { + TEST_STRG(tok->token_string, "Hello,\n World!\n\nYours,\n GraphQL."); + } + + tokens = tal_free(tokens); + + // Test the parser. + struct graphql_executable_document *doc; + TEST_CONT(graphql_lex(NULL, source, &tokens) == NULL); + TEST_CONT(graphql_parse(tokens, &doc) == NULL); + TEST_ABRT(doc != NULL); + tokens = tal_free(tokens); +} + +void check_example_29(char *source) { + struct list_head *tokens; + struct graphql_token *tok; + + if (!mute) printf("// Example No. 29\n"); + + sprintf(source, "\ +{\n\ + field(arg: null)\n\ + field\n\ +}\n\ + "); + + // Test the lexer. + TEST_CONT(graphql_lex(NULL, source, &tokens) == NULL); + TEST_ABRT(listlen(tokens) == 9); + tok = list_pop(tokens, struct graphql_token, node); TEST_CONT(tok->token_type == '{'); + tok = list_pop(tokens, struct graphql_token, node); TEST_CONT(tok->token_type == 'a'); + tok = list_pop(tokens, struct graphql_token, node); TEST_CONT(tok->token_type == '('); + tok = list_pop(tokens, struct graphql_token, node); TEST_CONT(tok->token_type == 'a'); + tok = list_pop(tokens, struct graphql_token, node); TEST_CONT(tok->token_type == ':'); + tok = list_pop(tokens, struct graphql_token, node); TEST_CONT(tok->token_type == 'a'); + TEST_STRG(tok->token_string, "null"); + tok = list_pop(tokens, struct graphql_token, node); TEST_CONT(tok->token_type == ')'); + tok = list_pop(tokens, struct graphql_token, node); TEST_CONT(tok->token_type == 'a'); + tok = list_pop(tokens, struct graphql_token, node); TEST_CONT(tok->token_type == '}'); + tok = list_pop(tokens, struct graphql_token, node); TEST_CONT(tok == NULL); + tokens = tal_free(tokens); + + // Test the parser. + struct graphql_executable_document *doc; + TEST_CONT(graphql_lex(NULL, source, &tokens) == NULL); + TEST_ABRT(graphql_parse(tokens, &doc) == NULL); + TEST_ABRT(doc != NULL); + TEST_ABRT(doc->first_def != NULL); + TEST_CONT(doc->first_def->next_def == NULL); + TEST_CONT(doc->first_def->frag_def == NULL); + TEST_ABRT(doc->first_def->op_def != NULL); + TEST_CONT(doc->first_def->op_def->op_type == NULL); + TEST_CONT(doc->first_def->op_def->op_name == NULL); + TEST_CONT(doc->first_def->op_def->vars == NULL); + TEST_CONT(doc->first_def->op_def->directives == NULL); + TEST_ABRT(doc->first_def->op_def->sel_set != NULL); + TEST_ABRT(doc->first_def->op_def->sel_set->first != NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->frag_spread == NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->inline_frag == NULL); + TEST_ABRT(doc->first_def->op_def->sel_set->first->field != NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->alias == NULL); + TEST_ABRT(doc->first_def->op_def->sel_set->first->field->name != NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->name->token_type == 'a'); + TEST_STRG(doc->first_def->op_def->sel_set->first->field->name->token_string, "field"); + TEST_ABRT(doc->first_def->op_def->sel_set->first->field->args != NULL); + TEST_ABRT(doc->first_def->op_def->sel_set->first->field->args->first != NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->args->first->next == NULL); + TEST_ABRT(doc->first_def->op_def->sel_set->first->field->args->first->name != NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->args->first->name->token_type == 'a'); + TEST_STRG(doc->first_def->op_def->sel_set->first->field->args->first->name->token_string, "arg"); + TEST_ABRT(doc->first_def->op_def->sel_set->first->field->args->first->val != NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->args->first->val->var == NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->args->first->val->int_val == NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->args->first->val->float_val == NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->args->first->val->bool_val == NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->args->first->val->enum_val == NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->args->first->val->list_val == NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->args->first->val->obj_val == NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->args->first->val->str_val == NULL); + TEST_ABRT(doc->first_def->op_def->sel_set->first->field->args->first->val->null_val != NULL); + TEST_ABRT(doc->first_def->op_def->sel_set->first->field->args->first->val->null_val->val != NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->args->first->val->null_val->val->token_type == 'a'); + TEST_STRG(doc->first_def->op_def->sel_set->first->field->args->first->val->null_val->val->token_string, "null"); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->directives == NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->sel_set == NULL); + TEST_ABRT(doc->first_def->op_def->sel_set->first->next != NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->next->frag_spread == NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->next->inline_frag == NULL); + TEST_ABRT(doc->first_def->op_def->sel_set->first->next->field != NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->next->field->alias == NULL); + TEST_ABRT(doc->first_def->op_def->sel_set->first->next->field->name != NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->next->field->name->token_type == 'a'); + TEST_STRG(doc->first_def->op_def->sel_set->first->next->field->name->token_string, "field"); + TEST_CONT(doc->first_def->op_def->sel_set->first->next->field->args == NULL); + tokens = tal_free(tokens); +} + +void check_example_30_and_31(const char *source) { + struct list_head *tokens; + struct graphql_token *tok; + + if (!mute) printf("// Example No. 30 and 31\n"); + + TEST_CONT(graphql_lex(NULL, source, &tokens) == NULL); + TEST_ABRT(listlen(tokens) == 15); + while ((tok = list_pop(tokens, struct graphql_token, node)) && !(tok->token_type == 'a' && tok->token_string != NULL && streq(tok->token_string, "lat"))) { } + TEST_CONT(tok); + if (tok) { + TEST_CONT(tok->token_type == 'a'); + tok = list_pop(tokens, struct graphql_token, node); TEST_CONT(tok->token_type == ':'); + tok = list_pop(tokens, struct graphql_token, node); TEST_CONT(tok->token_type == 'f'); + TEST_STRG(tok->token_string, "-53.211"); + } + tokens = tal_free(tokens); + + TEST_CONT(graphql_lex(NULL, source, &tokens) == NULL); + while ((tok = list_pop(tokens, struct graphql_token, node)) && !(tok->token_type == 'a' && tok->token_string != NULL && streq(tok->token_string, "lon"))) { } + TEST_CONT(tok); + if (tok) { + TEST_CONT(tok->token_type == 'a'); + tok = list_pop(tokens, struct graphql_token, node); TEST_CONT(tok->token_type == ':'); + tok = list_pop(tokens, struct graphql_token, node); TEST_CONT(tok->token_type == 'f'); + TEST_STRG(tok->token_string, "12.43"); + } + tokens = tal_free(tokens); + + // Test the parser. + struct graphql_executable_document *doc; + const char *e; + TEST_CONT(graphql_lex(NULL, source, &tokens) == NULL); + TEST_CONT((e = graphql_parse(tokens, &doc)) == NULL); + if (e) printf("%s\n", e); + TEST_ABRT(doc != NULL); + TEST_ABRT(doc->first_def != NULL); + TEST_CONT(doc->first_def->next_def == NULL); + TEST_CONT(doc->first_def->frag_def == NULL); + TEST_ABRT(doc->first_def->op_def != NULL); + TEST_CONT(doc->first_def->op_def->op_type == NULL); + TEST_CONT(doc->first_def->op_def->op_name == NULL); + TEST_CONT(doc->first_def->op_def->vars == NULL); + TEST_CONT(doc->first_def->op_def->directives == NULL); + TEST_ABRT(doc->first_def->op_def->sel_set != NULL); + TEST_ABRT(doc->first_def->op_def->sel_set->first != NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->next == NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->frag_spread == NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->inline_frag == NULL); + TEST_ABRT(doc->first_def->op_def->sel_set->first->field != NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->alias == NULL); + TEST_ABRT(doc->first_def->op_def->sel_set->first->field->name != NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->name->token_type == 'a'); + TEST_STRG(doc->first_def->op_def->sel_set->first->field->name->token_string, "nearestThing"); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->directives == NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->sel_set == NULL); + TEST_ABRT(doc->first_def->op_def->sel_set->first->field->args != NULL); + TEST_ABRT(doc->first_def->op_def->sel_set->first->field->args->first != NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->args->first->next == NULL); + TEST_ABRT(doc->first_def->op_def->sel_set->first->field->args->first->name != NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->args->first->name->token_type == 'a'); + TEST_STRG(doc->first_def->op_def->sel_set->first->field->args->first->name->token_string, "location"); + TEST_ABRT(doc->first_def->op_def->sel_set->first->field->args->first->val != NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->args->first->val->var == NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->args->first->val->int_val == NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->args->first->val->float_val == NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->args->first->val->bool_val == NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->args->first->val->enum_val == NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->args->first->val->list_val == NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->args->first->val->null_val == NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->args->first->val->str_val == NULL); + TEST_ABRT(doc->first_def->op_def->sel_set->first->field->args->first->val->obj_val != NULL); + TEST_ABRT(doc->first_def->op_def->sel_set->first->field->args->first->val->obj_val->first != NULL); + TEST_ABRT(doc->first_def->op_def->sel_set->first->field->args->first->val->obj_val->first->name != NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->args->first->val->obj_val->first->name->token_type == 'a'); + TEST_ABRT(doc->first_def->op_def->sel_set->first->field->args->first->val->obj_val->first->val != NULL); + TEST_ABRT(doc->first_def->op_def->sel_set->first->field->args->first->val->obj_val->first->val->float_val != NULL); + TEST_ABRT(doc->first_def->op_def->sel_set->first->field->args->first->val->obj_val->first->val->float_val->val != NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->args->first->val->obj_val->first->val->float_val->val->token_type == 'f'); + TEST_ABRT(doc->first_def->op_def->sel_set->first->field->args->first->val->obj_val->first->next != NULL); + TEST_ABRT(doc->first_def->op_def->sel_set->first->field->args->first->val->obj_val->first->next->name != NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->args->first->val->obj_val->first->next->name->token_type == 'a'); + TEST_ABRT(doc->first_def->op_def->sel_set->first->field->args->first->val->obj_val->first->next->val != NULL); + TEST_ABRT(doc->first_def->op_def->sel_set->first->field->args->first->val->obj_val->first->next->val->float_val != NULL); + TEST_ABRT(doc->first_def->op_def->sel_set->first->field->args->first->val->obj_val->first->next->val->float_val->val != NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->args->first->val->obj_val->first->next->val->float_val->val->token_type == 'f'); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->args->first->val->obj_val->first->next->next == NULL); + tokens = tal_free(tokens); +} + +void check_example_32(char *source) { + struct list_head *tokens; + struct graphql_token *tok; + + if (!mute) printf("// Example No. 32\n"); + + sprintf(source, "\ +query getZuckProfile($devicePicSize: Int) {\n\ + user(id: 4) {\n\ + id\n\ + name\n\ + profilePic(size: $devicePicSize)\n\ + }\n\ +}\n\ + "); + + // Test the lexer. + TEST_CONT(graphql_lex(NULL, source, &tokens) == NULL); + TEST_ABRT(listlen(tokens) == 27); + tok = list_pop(tokens, struct graphql_token, node); TEST_CONT(tok->token_type == 'a'); + tok = list_pop(tokens, struct graphql_token, node); TEST_CONT(tok->token_type == 'a'); + tok = list_pop(tokens, struct graphql_token, node); TEST_CONT(tok->token_type == '('); + tok = list_pop(tokens, struct graphql_token, node); TEST_CONT(tok->token_type == '$'); + tok = list_pop(tokens, struct graphql_token, node); TEST_CONT(tok->token_type == 'a'); + tok = list_pop(tokens, struct graphql_token, node); TEST_CONT(tok->token_type == ':'); + tok = list_pop(tokens, struct graphql_token, node); TEST_CONT(tok->token_type == 'a'); + tok = list_pop(tokens, struct graphql_token, node); TEST_CONT(tok->token_type == ')'); + tok = list_pop(tokens, struct graphql_token, node); TEST_CONT(tok->token_type == '{'); + tok = list_pop(tokens, struct graphql_token, node); TEST_CONT(tok->token_type == 'a'); + tok = list_pop(tokens, struct graphql_token, node); TEST_CONT(tok->token_type == '('); + tok = list_pop(tokens, struct graphql_token, node); TEST_CONT(tok->token_type == 'a'); + tok = list_pop(tokens, struct graphql_token, node); TEST_CONT(tok->token_type == ':'); + tok = list_pop(tokens, struct graphql_token, node); TEST_CONT(tok->token_type == 'i'); + tok = list_pop(tokens, struct graphql_token, node); TEST_CONT(tok->token_type == ')'); + tok = list_pop(tokens, struct graphql_token, node); TEST_CONT(tok->token_type == '{'); + tok = list_pop(tokens, struct graphql_token, node); TEST_CONT(tok->token_type == 'a'); + tok = list_pop(tokens, struct graphql_token, node); TEST_CONT(tok->token_type == 'a'); + tok = list_pop(tokens, struct graphql_token, node); TEST_CONT(tok->token_type == 'a'); + tok = list_pop(tokens, struct graphql_token, node); TEST_CONT(tok->token_type == '('); + tok = list_pop(tokens, struct graphql_token, node); TEST_CONT(tok->token_type == 'a'); + tok = list_pop(tokens, struct graphql_token, node); TEST_CONT(tok->token_type == ':'); + tok = list_pop(tokens, struct graphql_token, node); TEST_CONT(tok->token_type == '$'); + tok = list_pop(tokens, struct graphql_token, node); TEST_CONT(tok->token_type == 'a'); + tok = list_pop(tokens, struct graphql_token, node); TEST_CONT(tok->token_type == ')'); + tok = list_pop(tokens, struct graphql_token, node); TEST_CONT(tok->token_type == '}'); + tok = list_pop(tokens, struct graphql_token, node); TEST_CONT(tok->token_type == '}'); + tok = list_pop(tokens, struct graphql_token, node); TEST_CONT(tok == NULL); + tokens = tal_free(tokens); + + // Test the parser. + struct graphql_executable_document *doc; + TEST_CONT(graphql_lex(NULL, source, &tokens) == NULL); + TEST_ABRT(graphql_parse(tokens, &doc) == NULL); + TEST_ABRT(doc != NULL); + TEST_ABRT(doc->first_def != NULL); + TEST_CONT(doc->first_def->next_def == NULL); + TEST_CONT(doc->first_def->frag_def == NULL); + TEST_ABRT(doc->first_def->op_def != NULL); + TEST_ABRT(doc->first_def->op_def->op_type != NULL); + TEST_ABRT(doc->first_def->op_def->op_type->op_type != NULL); + TEST_CONT(doc->first_def->op_def->op_type->op_type->token_type == 'a'); + TEST_STRG(doc->first_def->op_def->op_type->op_type->token_string, "query"); + TEST_CONT(doc->first_def->op_def->op_name != NULL); + TEST_CONT(doc->first_def->op_def->op_name->token_type == 'a'); + TEST_STRG(doc->first_def->op_def->op_name->token_string, "getZuckProfile"); + TEST_ABRT(doc->first_def->op_def->vars != NULL); + TEST_ABRT(doc->first_def->op_def->vars->first != NULL); + TEST_CONT(doc->first_def->op_def->vars->first->next == NULL); + TEST_ABRT(doc->first_def->op_def->vars->first->var != NULL); + TEST_ABRT(doc->first_def->op_def->vars->first->var->name != NULL); + TEST_CONT(doc->first_def->op_def->vars->first->var->name->token_type == 'a'); + TEST_STRG(doc->first_def->op_def->vars->first->var->name->token_string, "devicePicSize"); + TEST_ABRT(doc->first_def->op_def->vars->first->type != NULL); +// TEST_CONT(doc->first_def->op_def->vars->first->type->list == NULL); +// TEST_CONT(doc->first_def->op_def->vars->first->type->non_null == NULL); + TEST_ABRT(doc->first_def->op_def->vars->first->type->named != NULL); + TEST_ABRT(doc->first_def->op_def->vars->first->type->named->name != NULL); + TEST_CONT(doc->first_def->op_def->vars->first->type->named->name->token_type == 'a'); + TEST_STRG(doc->first_def->op_def->vars->first->type->named->name->token_string, "Int"); + TEST_CONT(doc->first_def->op_def->vars->first->default_val == NULL); + TEST_CONT(doc->first_def->op_def->vars->first->directives == NULL); + TEST_CONT(doc->first_def->op_def->directives == NULL); + TEST_ABRT(doc->first_def->op_def->sel_set != NULL); + TEST_ABRT(doc->first_def->op_def->sel_set->first != NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->next == NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->frag_spread == NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->inline_frag == NULL); + TEST_ABRT(doc->first_def->op_def->sel_set->first->field != NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->alias == NULL); + TEST_ABRT(doc->first_def->op_def->sel_set->first->field->name != NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->name->token_type == 'a'); + TEST_STRG(doc->first_def->op_def->sel_set->first->field->name->token_string, "user"); + TEST_ABRT(doc->first_def->op_def->sel_set->first->field->args != NULL); + TEST_ABRT(doc->first_def->op_def->sel_set->first->field->args->first != NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->args->first->next == NULL); + TEST_ABRT(doc->first_def->op_def->sel_set->first->field->args->first->name != NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->args->first->name->token_type == 'a'); + TEST_STRG(doc->first_def->op_def->sel_set->first->field->args->first->name->token_string, "id"); + TEST_ABRT(doc->first_def->op_def->sel_set->first->field->args->first->val != NULL); + TEST_ABRT(doc->first_def->op_def->sel_set->first->field->args->first->val->int_val != NULL); + TEST_ABRT(doc->first_def->op_def->sel_set->first->field->args->first->val->int_val->val != NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->args->first->val->int_val->val->token_type == 'i'); + TEST_STRG(doc->first_def->op_def->sel_set->first->field->args->first->val->int_val->val->token_string, "4"); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->directives == NULL); + TEST_ABRT(doc->first_def->op_def->sel_set->first->field->sel_set != NULL); + TEST_ABRT(doc->first_def->op_def->sel_set->first->field->sel_set->first != NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->sel_set->first->frag_spread == NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->sel_set->first->inline_frag == NULL); + TEST_ABRT(doc->first_def->op_def->sel_set->first->field->sel_set->first->field != NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->sel_set->first->field->alias == NULL); + TEST_ABRT(doc->first_def->op_def->sel_set->first->field->sel_set->first->field->name != NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->sel_set->first->field->name->token_type == 'a'); + TEST_STRG(doc->first_def->op_def->sel_set->first->field->sel_set->first->field->name->token_string, "id"); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->sel_set->first->field->args == NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->sel_set->first->field->directives == NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->sel_set->first->field->sel_set == NULL); + TEST_ABRT(doc->first_def->op_def->sel_set->first->field->sel_set->first->next != NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->sel_set->first->next->frag_spread == NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->sel_set->first->next->inline_frag == NULL); + TEST_ABRT(doc->first_def->op_def->sel_set->first->field->sel_set->first->next->field != NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->sel_set->first->next->field->alias == NULL); + TEST_ABRT(doc->first_def->op_def->sel_set->first->field->sel_set->first->next->field->name != NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->sel_set->first->next->field->name->token_type == 'a'); + TEST_STRG(doc->first_def->op_def->sel_set->first->field->sel_set->first->next->field->name->token_string, "name"); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->sel_set->first->next->field->args == NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->sel_set->first->next->field->directives == NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->sel_set->first->next->field->sel_set == NULL); + TEST_ABRT(doc->first_def->op_def->sel_set->first->field->sel_set->first->next->next != NULL); + TEST_ABRT(doc->first_def->op_def->sel_set->first->field->sel_set->first->next->next->next == NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->sel_set->first->next->next->frag_spread == NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->sel_set->first->next->next->inline_frag == NULL); + TEST_ABRT(doc->first_def->op_def->sel_set->first->field->sel_set->first->next->next->field != NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->sel_set->first->next->next->field->alias == NULL); + TEST_ABRT(doc->first_def->op_def->sel_set->first->field->sel_set->first->next->next->field->name != NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->sel_set->first->next->next->field->name->token_type == 'a'); + TEST_STRG(doc->first_def->op_def->sel_set->first->field->sel_set->first->next->next->field->name->token_string, "profilePic"); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->sel_set->first->next->next->field->directives == NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->sel_set->first->next->next->field->sel_set == NULL); + TEST_ABRT(doc->first_def->op_def->sel_set->first->field->sel_set->first->next->next->field->args != NULL); + TEST_ABRT(doc->first_def->op_def->sel_set->first->field->sel_set->first->next->next->field->args->first != NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->sel_set->first->next->next->field->args->first->next == NULL); + TEST_ABRT(doc->first_def->op_def->sel_set->first->field->sel_set->first->next->next->field->args->first->name != NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->sel_set->first->next->next->field->args->first->name->token_type == 'a'); + TEST_STRG(doc->first_def->op_def->sel_set->first->field->sel_set->first->next->next->field->args->first->name->token_string, "size"); + TEST_ABRT(doc->first_def->op_def->sel_set->first->field->sel_set->first->next->next->field->args->first->val != NULL); + TEST_ABRT(doc->first_def->op_def->sel_set->first->field->sel_set->first->next->next->field->args->first->val->var != NULL); + TEST_ABRT(doc->first_def->op_def->sel_set->first->field->sel_set->first->next->next->field->args->first->val->var->name != NULL); + TEST_CONT(doc->first_def->op_def->sel_set->first->field->sel_set->first->next->next->field->args->first->val->var->name->token_type == 'a'); + TEST_STRG(doc->first_def->op_def->sel_set->first->field->sel_set->first->next->next->field->args->first->val->var->name->token_string, "devicePicSize"); + tokens = tal_free(tokens); +} + +void check_example_34(char *source) { + struct list_head *tokens; + struct graphql_token *tok; + + if (!mute) printf("// Example No. 34\n"); + + sprintf(source, "\ +type Person\n\ + @addExternalFields(source: \"profiles\")\n\ + @excludeField(name: \"photo\") {\n\ + name: String\n\ +}\n\ + "); + + // Test the lexer. + TEST_CONT(graphql_lex(NULL, source, &tokens) == NULL); + TEST_ABRT(listlen(tokens) == 21); + tok = list_pop(tokens, struct graphql_token, node); TEST_CONT(tok->token_type == 'a'); + tok = list_pop(tokens, struct graphql_token, node); TEST_CONT(tok->token_type == 'a'); + tok = list_pop(tokens, struct graphql_token, node); TEST_CONT(tok->token_type == '@'); + tok = list_pop(tokens, struct graphql_token, node); TEST_CONT(tok->token_type == 'a'); + TEST_STRG(tok->token_string, "addExternalFields"); + tok = list_pop(tokens, struct graphql_token, node); TEST_CONT(tok->token_type == '('); + tok = list_pop(tokens, struct graphql_token, node); TEST_CONT(tok->token_type == 'a'); + tok = list_pop(tokens, struct graphql_token, node); TEST_CONT(tok->token_type == ':'); + tok = list_pop(tokens, struct graphql_token, node); TEST_CONT(tok->token_type == 's'); + tok = list_pop(tokens, struct graphql_token, node); TEST_CONT(tok->token_type == ')'); + tok = list_pop(tokens, struct graphql_token, node); TEST_CONT(tok->token_type == '@'); + tok = list_pop(tokens, struct graphql_token, node); TEST_CONT(tok->token_type == 'a'); + TEST_STRG(tok->token_string, "excludeField"); + tok = list_pop(tokens, struct graphql_token, node); TEST_CONT(tok->token_type == '('); + tok = list_pop(tokens, struct graphql_token, node); TEST_CONT(tok->token_type == 'a'); + tok = list_pop(tokens, struct graphql_token, node); TEST_CONT(tok->token_type == ':'); + tok = list_pop(tokens, struct graphql_token, node); TEST_CONT(tok->token_type == 's'); + tok = list_pop(tokens, struct graphql_token, node); TEST_CONT(tok->token_type == ')'); + tok = list_pop(tokens, struct graphql_token, node); TEST_CONT(tok->token_type == '{'); + tok = list_pop(tokens, struct graphql_token, node); TEST_CONT(tok->token_type == 'a'); + tok = list_pop(tokens, struct graphql_token, node); TEST_CONT(tok->token_type == ':'); + tok = list_pop(tokens, struct graphql_token, node); TEST_CONT(tok->token_type == 'a'); + tok = list_pop(tokens, struct graphql_token, node); TEST_CONT(tok->token_type == '}'); + tok = list_pop(tokens, struct graphql_token, node); TEST_CONT(tok == NULL); + tokens = tal_free(tokens); + + // The type system is not yet implemented, so parsing will fail here. + // This could be "phase 2" of this project. + struct graphql_executable_document *doc; + TEST_CONT(graphql_lex(NULL, source, &tokens) == NULL); + TEST_CONT(graphql_parse(tokens, &doc) != NULL); + tokens = tal_free(tokens); +} + +void check_example_35(char *source) { + struct list_head *tokens; + struct graphql_token *tok; + + if (!mute) printf("// Example No. 35\n"); + + sprintf(source, "\ +type Person\n\ + @excludeField(name: \"photo\")\n\ + @addExternalFields(source: \"profiles\") {\n\ + name: String\n\ +}\n\ + "); + + TEST_CONT(graphql_lex(NULL, source, &tokens) == NULL); + TEST_ABRT(listlen(tokens) == 21); + tok = list_pop(tokens, struct graphql_token, node); TEST_CONT(tok->token_type == 'a'); + tok = list_pop(tokens, struct graphql_token, node); TEST_CONT(tok->token_type == 'a'); + tok = list_pop(tokens, struct graphql_token, node); TEST_CONT(tok->token_type == '@'); + tok = list_pop(tokens, struct graphql_token, node); TEST_CONT(tok->token_type == 'a'); + TEST_STRG(tok->token_string, "excludeField"); + tok = list_pop(tokens, struct graphql_token, node); TEST_CONT(tok->token_type == '('); + tok = list_pop(tokens, struct graphql_token, node); TEST_CONT(tok->token_type == 'a'); + tok = list_pop(tokens, struct graphql_token, node); TEST_CONT(tok->token_type == ':'); + tok = list_pop(tokens, struct graphql_token, node); TEST_CONT(tok->token_type == 's'); + tok = list_pop(tokens, struct graphql_token, node); TEST_CONT(tok->token_type == ')'); + tok = list_pop(tokens, struct graphql_token, node); TEST_CONT(tok->token_type == '@'); + tok = list_pop(tokens, struct graphql_token, node); TEST_CONT(tok->token_type == 'a'); + TEST_STRG(tok->token_string, "addExternalFields"); + tok = list_pop(tokens, struct graphql_token, node); TEST_CONT(tok->token_type == '('); + tok = list_pop(tokens, struct graphql_token, node); TEST_CONT(tok->token_type == 'a'); + tok = list_pop(tokens, struct graphql_token, node); TEST_CONT(tok->token_type == ':'); + tok = list_pop(tokens, struct graphql_token, node); TEST_CONT(tok->token_type == 's'); + tok = list_pop(tokens, struct graphql_token, node); TEST_CONT(tok->token_type == ')'); + tok = list_pop(tokens, struct graphql_token, node); TEST_CONT(tok->token_type == '{'); + tok = list_pop(tokens, struct graphql_token, node); TEST_CONT(tok->token_type == 'a'); + tok = list_pop(tokens, struct graphql_token, node); TEST_CONT(tok->token_type == ':'); + tok = list_pop(tokens, struct graphql_token, node); TEST_CONT(tok->token_type == 'a'); + tok = list_pop(tokens, struct graphql_token, node); TEST_CONT(tok->token_type == '}'); + tok = list_pop(tokens, struct graphql_token, node); TEST_CONT(tok == NULL); + tokens = tal_free(tokens); + + // The type system is not yet implemented, so parsing will fail here. + // This could be "phase 2" of this project. + struct graphql_executable_document *doc; + TEST_CONT(graphql_lex(NULL, source, &tokens) == NULL); + TEST_CONT(graphql_parse(tokens, &doc) != NULL); + tokens = tal_free(tokens); +} + +/* End of test case functions. */ + +/* Beginning of main() test run to run all test cases. */ + +int main(void) +{ + printf("\nTesting GraphQL lexer/parser...\n"); + + char source[1024]; + int prev_fail; // Used by RUNx macros. + + // Check the lexer with all valid line terminators. + const char *new_line = "\n"; + const char *carriage_return = "\r"; + const char *carriage_return_new_line = "\r\n"; + const char *line_terminators[] = { new_line, carriage_return, carriage_return_new_line }; + for (int i=0; i<3; i++) { + sprintf(source, "\ +{%s\ + user(id: 4) {%s\ + name%s\ + }%s\ +}%s\ + ", line_terminators[i], line_terminators[i], line_terminators[i], line_terminators[i], line_terminators[i]); + + RUN(check_example_3); + } + + RUN(check_example_5); // Parse a mutation operation and check results. + + RUN(check_example_6); // Parse an unnamed query and check results. + + RUN(check_example_7); // Parse multiple fields in a selection set and check results. + RUN(check_example_8); // Parse complex data structure and check results. + RUN(check_example_9); // Parse example of top-level fields and check results. + RUN(check_example_10); // Parse example with parameterized field and check results. + RUN(check_example_11); // Parse example with multiple field arguments and check results. + + // Parse examples of different parameter order and check for identical results. + sprintf(source, "\ +{\n\ + picture(width: 200, height: 100)\n\ +}\n\ + "); + RUN(check_example_12_and_13); + sprintf(source, "\ +{\n\ + picture(height: 100, width: 200)\n\ +}\n\ + "); + RUN(check_example_12_and_13); + + RUN(check_example_14); // Parse alias example and check results. + RUN(check_example_16); // Parse a top-level-field alias example and check results. + + RUN(check_example_18); // Parse example and check results. + + RUN(check_example_19); // Parse fragment example and check results. + RUN(check_example_20); // Parse another fragment example and check results. + RUN(check_example_21); // Parse fragment typing example and check results. + RUN(check_example_23); // Parse fragment typing example and check results. + RUN(check_example_24); // Parse fragment typing example and check results. + + // Parse various int values and check results. + for (int i= -15; i<= 15; i++) { RUN1(check_int_value, i); } + for (int i= -32770; i<= -32765; i++) { RUN1(check_int_value, i); } + for (int i= 32765; i<= 32770; i++) { RUN1(check_int_value, i); } + for (int i=-2147483648; i<=-2147483645; i++) { RUN1(check_int_value, i); } + for (int i= 2147483647; i>= 2147483645; i--) { RUN1(check_int_value, i); } + RUN(check_invalid_int_values); + + // Parse various float values and check results. + for (float i= -1.0; i<= 1.0; i+= 0.1) { RUN2(check_float_value, i, "%1.1f"); } + for (float i=-327.70; i<=-327.65; i+= 0.01) { RUN2(check_float_value, i, "%1.2f"); } + for (float i= 327.65; i<= 327.70; i+= 0.01) { RUN2(check_float_value, i, "%1.2f"); } + for (float i= -5e-20; i<= -1e-20; i+= 1e-20) { RUN2(check_float_value, i, "%1.0e"); } + for (float i= 5e-20; i>= 1e-20; i-= 1e-20) { RUN2(check_float_value, i, "%1.0e"); } + for (float i= 5E+20; i>= 1E+20; i-= 1E+20) { RUN2(check_float_value, i, "%1.2E"); } + for (float i=1.5E+20; i>=1.1E+20; i-=0.1E+20) { RUN2(check_float_value, i, "%1.1E"); } + RUN(check_valid_float_values); + RUN(check_invalid_float_values); + + RUN(check_boolean_values); // Parse boolean values and check results. + + // Parse various string values and check results. + RUN2(check_string_value, "te^st", NULL ); // Missing quotes (the caret makes it an invalid token for testing purposes). + RUN2(check_string_value, "\"te^st\"", "te^st" ); // A valid string. + RUN2(check_string_value, "\"\"", "" ); // An empty string is valid. + RUN2(check_string_value, "\"\"\"te^st\"\"\"", "te^st" ); // A block string. + RUN2(check_string_value, "\"te\\st\"", NULL ); // Backslashes are normally invalid. + RUN2(check_string_value, "\"te\nst\"", NULL ); // New-line characters are invalid except in block strings. + RUN2(check_string_value, "\"te\rst\"", NULL ); // New-line characters are invalid except in block strings. + RUN2(check_string_value, "\"\"\"te\nst\"\"\"", "te\nst" ); // New-line characters are valid in block strings. + RUN2(check_string_value, "\"\"\"te\rst\"\"\"", "te\rst" ); // New-line characters are valid in block strings. + RUN2(check_string_value, "\"te\"st\"", NULL ); // A quote in a string is invalid. + RUN2(check_string_value, "\"te\\\"st\"", "te\"st" ); // ...unless it is escaped. + RUN2(check_string_value, "\"\"\"te\"st\"\"\"", "te\"st" ); // A quote in a block string is valid. + RUN2(check_string_value, "\"\"\"te\"\"st\"\"\"", "te\"\"st" ); // It is even valid to have two quotes in a block string. + RUN2(check_string_value, "\"\"\"te\"\"\"st\"\"\"", NULL ); // Three quotes in a row are not allowed in a block string. + RUN2(check_string_value, "\"\"\"te\\\"\"\"st\"\"\"", "te\"\"\"st" ); // ...unless escaped. + RUN2(check_string_value, "\"te\\\"st\"", "te\"st" ); // Check escape sequence. + RUN2(check_string_value, "\"te\\\\st\"", "te\\st" ); // Check escape sequence. + RUN2(check_string_value, "\"te\\/st\"", "te/st" ); // Check escape sequence. + RUN2(check_string_value, "\"te\\bst\"", "te\bst" ); // Check escape sequence. + RUN2(check_string_value, "\"te\\fst\"", "te\fst" ); // Check escape sequence. + RUN2(check_string_value, "\"te\\nst\"", "te\nst" ); // Check escape sequence. + RUN2(check_string_value, "\"te\\rst\"", "te\rst" ); // Check escape sequence. + RUN2(check_string_value, "\"te\\tst\"", "te\tst" ); // Check escape sequence. + RUN2(check_string_value, "\"te\\vst\"", NULL ); // Invalid escape sequence. + RUN2(check_string_value, "\"te\\033st\"", NULL ); // Invalid escape sequence. + // Note: Unicode excape sequence is tested below. + + // This block string and this string should result in identical tokens. + sprintf(source, "\ +mutation {\n\ + sendEmail(message: \"\"\"\n\ + Hello,\n\ + World!\n\ +\n\ + Yours,\n\ + GraphQL.\n\ + \"\"\")\n\ +}\n\ + "); + RUN(check_example_25_and_26); + sprintf(source, "\ +mutation {\n\ + sendEmail(message: \"Hello,\\n World!\\n\\nYours,\\n GraphQL.\")\n\ +}\n\ + "); + RUN(check_example_25_and_26); + + // Check block string example. + RUN2(check_string_value, +"\"\"\"\n\ +This starts with and ends with an empty line,\n\ +which makes it easier to read.\n\ +\"\"\"", + "This starts with and ends with an empty line,\nwhich makes it easier to read."); + + // Check block string counter example. + RUN2(check_string_value, +"\"\"\"This does not start with or end with any empty lines,\n\ +which makes it a little harder to read.\"\"\"", + "This does not start with or end with any empty lines,\nwhich makes it a little harder to read."); + + RUN2(check_string_value, "\"te\\u001bst\"", "te\033st" ); // Check unicode escape sequence. + RUN2(check_string_value, "\"te\\u001Bst\"", "te\033st" ); // Check again with other case. + RUN2(check_string_value, "\"\"\"te\\u001bst\"\"\"", "te\\u001bst" ); // Escape sequences are ignored in block strings (except for the triple quote). + RUN2(check_string_value, "\"\"\"te\\nst\"\"\"", "te\\nst" ); // Escape sequences are ignored in block strings (except for the triple quote). + RUN2(check_string_value, "\"te\\u2026st\"", "te\xe2\x80\xa6st"); // Check a unicode escape sequence. + + RUN(check_example_29); // Parse null value and check result. + + // These two input objects should have the same result. + sprintf(source, "\ +{\n\ + nearestThing(location: { lon: 12.43, lat: -53.211 })\n\ +}\n\ + "); + RUN(check_example_30_and_31); + sprintf(source, "\ +{\n\ + nearestThing(location: { lat: -53.211, lon: 12.43 })\n\ +}\n\ + "); + RUN(check_example_30_and_31); + + RUN(check_example_32); // Parse an example with a variable and check result. + + RUN(check_example_34); // Parse directives and check result. + RUN(check_example_35); // Parse directives and check result. + + RUN(check_example_35); // Parse directives and check result. + + printf("total passed: %d\n%stotal failed: %d\033[0m\n", pass, fail?"\033[91m":"", fail); + + return fail==0? 0: 1; +} + diff --git a/ccan/htable/htable.c b/ccan/htable/htable.c index cffd0619..f631ffeb 100644 --- a/ccan/htable/htable.c +++ b/ccan/htable/htable.c @@ -86,14 +86,16 @@ void htable_init(struct htable *ht, ht->table = &ht->common_bits; } +/* Fill to 87.5% */ static inline size_t ht_max(const struct htable *ht) { - return ((size_t)3 << ht->bits) / 4; + return ((size_t)7 << ht->bits) / 8; } -static inline size_t ht_max_with_deleted(const struct htable *ht) +/* Clean deleted if we're full, and more than 12.5% deleted */ +static inline size_t ht_max_deleted(const struct htable *ht) { - return ((size_t)9 << ht->bits) / 10; + return ((size_t)1 << ht->bits) / 8; } bool htable_init_sized(struct htable *ht, @@ -103,7 +105,7 @@ bool htable_init_sized(struct htable *ht, htable_init(ht, rehash, priv); /* Don't go insane with sizing. */ - for (ht->bits = 1; ((size_t)3 << ht->bits) / 4 < expect; ht->bits++) { + for (ht->bits = 1; ht_max(ht) < expect; ht->bits++) { if (ht->bits == 30) break; } @@ -195,12 +197,83 @@ void *htable_prev_(const struct htable *ht, struct htable_iter *i) for (;;) { if (!i->off) return NULL; - i->off --; + i->off--; if (entry_is_valid(ht->table[i->off])) return get_raw_ptr(ht, ht->table[i->off]); } } +/* Another bit currently in mask needs to be exposed, so that a bucket with p in + * it won't appear invalid */ +static COLD void unset_another_common_bit(struct htable *ht, + uintptr_t *maskdiff, + const void *p) +{ + size_t i; + + for (i = sizeof(uintptr_t) * CHAR_BIT - 1; i > 0; i--) { + if (((uintptr_t)p & ((uintptr_t)1 << i)) + && ht->common_mask & ~*maskdiff & ((uintptr_t)1 << i)) + break; + } + /* There must have been one, right? */ + assert(i > 0); + + *maskdiff |= ((uintptr_t)1 << i); +} + +/* We want to change the common mask: this fixes up the table */ +static COLD void fixup_table_common(struct htable *ht, uintptr_t maskdiff) +{ + size_t i; + uintptr_t bitsdiff; + +again: + bitsdiff = ht->common_bits & maskdiff; + + for (i = 0; i < (size_t)1 << ht->bits; i++) { + uintptr_t e; + if (!entry_is_valid(e = ht->table[i])) + continue; + + /* Clear the bits no longer in the mask, set them as + * expected. */ + e &= ~maskdiff; + e |= bitsdiff; + /* If this made it invalid, restart with more exposed */ + if (!entry_is_valid(e)) { + unset_another_common_bit(ht, &maskdiff, get_raw_ptr(ht, e)); + goto again; + } + ht->table[i] = e; + } + + /* Take away those bits from our mask, bits and perfect bit. */ + ht->common_mask &= ~maskdiff; + ht->common_bits &= ~maskdiff; + if (ht_perfect_mask(ht) & maskdiff) + ht->perfect_bitnum = NO_PERFECT_BIT; +} + +/* Limited recursion */ +static void ht_add(struct htable *ht, const void *new, size_t h); + +/* We tried to add this entry, but it looked invalid! We need to + * let another pointer bit through mask */ +static COLD void update_common_fix_invalid(struct htable *ht, const void *p, size_t h) +{ + uintptr_t maskdiff; + + assert(ht->elems != 0); + + maskdiff = 0; + unset_another_common_bit(ht, &maskdiff, p); + fixup_table_common(ht, maskdiff); + + /* Now won't recurse */ + ht_add(ht, p, h); +} + /* This does not expand the hash table, that's up to caller. */ static void ht_add(struct htable *ht, const void *new, size_t h) { @@ -214,6 +287,8 @@ static void ht_add(struct htable *ht, const void *new, size_t h) i = (i + 1) & ((1 << ht->bits)-1); } ht->table[i] = make_hval(ht, new, get_hash_ptr_bits(ht, h)|perfect); + if (!entry_is_valid(ht->table[i])) + update_common_fix_invalid(ht, new, h); } static COLD bool double_table(struct htable *ht) @@ -283,20 +358,10 @@ static COLD void rehash_table(struct htable *ht) /* We stole some bits, now we need to put them back... */ static COLD void update_common(struct htable *ht, const void *p) { - unsigned int i; - uintptr_t maskdiff, bitsdiff; + uintptr_t maskdiff; if (ht->elems == 0) { - /* Always reveal one bit of the pointer in the bucket, - * so it's not zero or HTABLE_DELETED (1), even if - * hash happens to be 0. Assumes (void *)1 is not a - * valid pointer. */ - for (i = sizeof(uintptr_t)*CHAR_BIT - 1; i > 0; i--) { - if ((uintptr_t)p & ((uintptr_t)1 << i)) - break; - } - - ht->common_mask = ~((uintptr_t)1 << i); + ht->common_mask = -1; ht->common_bits = ((uintptr_t)p & ht->common_mask); ht->perfect_bitnum = 0; (void)htable_debug(ht, HTABLE_LOC); @@ -306,33 +371,25 @@ static COLD void update_common(struct htable *ht, const void *p) /* Find bits which are unequal to old common set. */ maskdiff = ht->common_bits ^ ((uintptr_t)p & ht->common_mask); - /* These are the bits which go there in existing entries. */ - bitsdiff = ht->common_bits & maskdiff; - - for (i = 0; i < (size_t)1 << ht->bits; i++) { - if (!entry_is_valid(ht->table[i])) - continue; - /* Clear the bits no longer in the mask, set them as - * expected. */ - ht->table[i] &= ~maskdiff; - ht->table[i] |= bitsdiff; - } - - /* Take away those bits from our mask, bits and perfect bit. */ - ht->common_mask &= ~maskdiff; - ht->common_bits &= ~maskdiff; - if (ht_perfect_mask(ht) & maskdiff) - ht->perfect_bitnum = NO_PERFECT_BIT; + fixup_table_common(ht, maskdiff); (void)htable_debug(ht, HTABLE_LOC); } bool htable_add_(struct htable *ht, size_t hash, const void *p) { - if (ht->elems+1 > ht_max(ht) && !double_table(ht)) - return false; - if (ht->elems+1 + ht->deleted > ht_max_with_deleted(ht)) - rehash_table(ht); + /* Cannot insert NULL, or (void *)1. */ assert(p); + assert(entry_is_valid((uintptr_t)p)); + + /* Getting too full? */ + if (ht->elems+1 + ht->deleted > ht_max(ht)) { + /* If we're more than 1/8 deleted, clean those, + * otherwise double table size. */ + if (ht->deleted > ht_max_deleted(ht)) + rehash_table(ht); + else if (!double_table(ht)) + return false; + } if (((uintptr_t)p & ht->common_mask) != ht->common_bits) update_common(ht, p); @@ -361,8 +418,12 @@ void htable_delval_(struct htable *ht, struct htable_iter *i) assert(entry_is_valid(ht->table[i->off])); ht->elems--; - ht->table[i->off] = HTABLE_DELETED; - ht->deleted++; + /* Cheap test: if the next bucket is empty, don't need delete marker */ + if (ht->table[hash_bucket(ht, i->off+1)] != 0) { + ht->table[i->off] = HTABLE_DELETED; + ht->deleted++; + } else + ht->table[i->off] = 0; } void *htable_pick_(const struct htable *ht, size_t seed, struct htable_iter *i) diff --git a/ccan/htable/htable.h b/ccan/htable/htable.h index eac57e37..faaf541b 100644 --- a/ccan/htable/htable.h +++ b/ccan/htable/htable.h @@ -132,7 +132,7 @@ bool htable_copy_(struct htable *dst, const struct htable *src); * htable_add - add a pointer into a hash table. * @ht: the htable * @hash: the hash value of the object - * @p: the non-NULL pointer + * @p: the non-NULL pointer (also cannot be (void *)1). * * Also note that this can only fail due to allocation failure. Otherwise, it * returns true. diff --git a/ccan/htable/htable_type.h b/ccan/htable/htable_type.h index de4cd471..0aacb7f3 100644 --- a/ccan/htable/htable_type.h +++ b/ccan/htable/htable_type.h @@ -35,6 +35,9 @@ * bool _del(struct *ht, const *e); * bool _delkey(struct *ht, const *k); * + * Delete by iterator: + * bool _delval(struct *ht, struct _iter *i); + * * Find and return the (first) matching element, or NULL: * type *_get(const struct @name *ht, const *k); * @@ -147,12 +150,16 @@ return name##_del(ht, elem); \ return false; \ } \ - static inline UNNEEDED bool name##_pick(const struct name *ht, \ + static inline UNNEEDED void name##_delval(struct name *ht, \ + struct name##_iter *iter) \ + { \ + htable_delval(&ht->raw, &iter->i); \ + } \ + static inline UNNEEDED type *name##_pick(const struct name *ht, \ size_t seed, \ struct name##_iter *iter) \ { \ - /* Note &iter->i == NULL iff iter is NULL */ \ - return htable_pick(&ht->raw, seed, &iter->i); \ + return htable_pick(&ht->raw, seed, iter ? &iter->i : NULL); \ } \ static inline UNNEEDED type *name##_first(const struct name *ht, \ struct name##_iter *iter) \ diff --git a/ccan/htable/test/run-clash.c b/ccan/htable/test/run-clash.c new file mode 100644 index 00000000..a4e5d38a --- /dev/null +++ b/ccan/htable/test/run-clash.c @@ -0,0 +1,31 @@ +#include +#include +#include +#include +#include + +/* Clashy hash */ +static size_t hash(const void *elem, void *unused UNNEEDED) +{ + return 0; +} + +int main(void) +{ + struct htable ht; + + plan_tests(254 * 253); + /* We try to get two elements which clash */ + for (size_t i = 2; i < 256; i++) { + for (size_t j = 2; j < 256; j++) { + if (i == j) + continue; + htable_init(&ht, hash, NULL); + htable_add(&ht, hash((void *)i, NULL), (void *)i); + htable_add(&ht, hash((void *)j, NULL), (void *)j); + ok1(htable_check(&ht, "test")); + htable_clear(&ht); + } + } + return exit_status(); +} diff --git a/ccan/htable/test/run-debug.c b/ccan/htable/test/run-debug.c index b9f48ee7..39991035 100644 --- a/ccan/htable/test/run-debug.c +++ b/ccan/htable/test/run-debug.c @@ -107,7 +107,7 @@ static bool check_mask(struct htable *ht, uint64_t val[], unsigned num) int main(void) { - unsigned int i, weight; + unsigned int i; uintptr_t perfect_bit; struct htable ht; uint64_t val[NUM_VALS]; @@ -131,14 +131,7 @@ int main(void) add_vals(&ht, val, 0, 1); ok1(ht.bits == 1); ok1(ht_max(&ht) == 1); - weight = 0; - for (i = 0; i < sizeof(ht.common_mask) * CHAR_BIT; i++) { - if (ht.common_mask & ((uintptr_t)1 << i)) { - weight++; - } - } - /* Only one bit should be clear. */ - ok1(weight == i-1); + ok1(ht.common_mask == -1); /* Mask should be set. */ ok1(check_mask(&ht, val, 1)); diff --git a/ccan/htable/test/run.c b/ccan/htable/test/run.c index ada85f95..27007a41 100644 --- a/ccan/htable/test/run.c +++ b/ccan/htable/test/run.c @@ -97,7 +97,7 @@ static bool check_mask(struct htable *ht, uint64_t val[], unsigned num) int main(void) { - unsigned int i, weight; + unsigned int i; uintptr_t perfect_bit; struct htable ht; uint64_t val[NUM_VALS]; @@ -122,14 +122,7 @@ int main(void) add_vals(&ht, val, 0, 1); ok1(ht.bits == 1); ok1(ht_max(&ht) == 1); - weight = 0; - for (i = 0; i < sizeof(ht.common_mask) * CHAR_BIT; i++) { - if (ht.common_mask & ((uintptr_t)1 << i)) { - weight++; - } - } - /* Only one bit should be clear. */ - ok1(weight == i-1); + ok1(ht.common_mask == -1); /* Mask should be set. */ ok1(check_mask(&ht, val, 1)); diff --git a/ccan/htable/tools/Makefile b/ccan/htable/tools/Makefile index a2cad59f..c8a428a7 100644 --- a/ccan/htable/tools/Makefile +++ b/ccan/htable/tools/Makefile @@ -4,9 +4,10 @@ CFLAGS=-Wall -Werror -O3 -I$(CCANDIR) CCAN_OBJS:=ccan-tal.o ccan-tal-str.o ccan-tal-grab_file.o ccan-take.o ccan-time.o ccan-str.o ccan-noerr.o ccan-list.o -all: speed stringspeed hsearchspeed +all: speed stringspeed hsearchspeed density speed: speed.o hash.o $(CCAN_OBJS) +density: density.o hash.o $(CCAN_OBJS) speed.o: speed.c ../htable.h ../htable.c diff --git a/ccan/htable/tools/density.c b/ccan/htable/tools/density.c new file mode 100644 index 00000000..5f7400b9 --- /dev/null +++ b/ccan/htable/tools/density.c @@ -0,0 +1,107 @@ +/* Density measurements for hashtables. */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* We don't actually hash objects: we put values in as if they were ptrs */ +static uintptr_t key(const ptrint_t *obj) +{ + return ptr2int(obj); +} + +static size_t hash_uintptr(uintptr_t key) +{ + return hashl(&key, 1, 0); +} + +static bool cmp(const ptrint_t *p, uintptr_t k) +{ + return key(p) == k; +} + +HTABLE_DEFINE_TYPE(ptrint_t, key, hash_uintptr, cmp, htable_ptrint); + +/* Nanoseconds per operation */ +static size_t normalize(const struct timeabs *start, + const struct timeabs *stop, + unsigned int num) +{ + return time_to_nsec(time_divide(time_between(*stop, *start), num)); +} + +static size_t average_run(const struct htable_ptrint *ht, size_t count, size_t *longest) +{ + size_t i, total = 0; + + *longest = 0; + for (i = 0; i < count; i++) { + size_t h = hash_uintptr(i + 2); + size_t run = 1; + + while (get_raw_ptr(&ht->raw, ht->raw.table[h % ((size_t)1 << ht->raw.bits)]) != int2ptr(i + 2)) { + h++; + run++; + } + total += run; + if (run > *longest) + *longest = run; + } + return total / count; +} + +int main(int argc, char *argv[]) +{ + unsigned int i; + size_t num; + struct timeabs start, stop; + struct htable_ptrint ht; + + if (argc != 2) + errx(1, "Usage: density "); + + num = atoi(argv[1]); + + printf("Total buckets, buckets used, nanoseconds search time per element, avg run, longest run\n"); + for (i = 1; i <= 99; i++) { + uintptr_t j; + struct htable_ptrint_iter it; + size_t count, avg_run, longest_run; + ptrint_t *p; + + htable_ptrint_init_sized(&ht, num * 3 / 4); + assert((1 << ht.raw.bits) == num); + + /* Can't put 0 or 1 in the hash table: multiply by a prime. */ + for (j = 0; j < num * i / 100; j++) { + htable_ptrint_add(&ht, int2ptr(j + 2)); + /* stop it from doubling! */ + ht.raw.elems = num / 2; + } + /* Must not have changed! */ + assert((1 << ht.raw.bits) == num); + + /* Clean cache */ + count = 0; + for (p = htable_ptrint_first(&ht, &it); p; p = htable_ptrint_next(&ht, &it)) + count++; + assert(count == num * i / 100); + start = time_now(); + for (j = 0; j < count; j++) + assert(htable_ptrint_get(&ht, j + 2)); + stop = time_now(); + avg_run = average_run(&ht, count, &longest_run); + printf("%zu,%zu,%zu,%zu,%zu\n", + num, count, normalize(&start, &stop, count), avg_run, longest_run); + fflush(stdout); + htable_ptrint_clear(&ht); + } + + return 0; +} diff --git a/ccan/idtree/idtree.c b/ccan/idtree/idtree.c index 6e75450e..e10c1723 100644 --- a/ccan/idtree/idtree.c +++ b/ccan/idtree/idtree.c @@ -42,9 +42,9 @@ #define MAX_LEVEL (MAX_ID_SHIFT + IDTREE_BITS - 1) / IDTREE_BITS #define IDTREE_FREE_MAX MAX_LEVEL + MAX_LEVEL -#define set_bit(bit, v) (v) |= (1<<(bit)) -#define clear_bit(bit, v) (v) &= ~(1<<(bit)) -#define test_bit(bit, v) ((v) & (1<<(bit))) +#define set_bit(bit, v) (v) |= (1U<<(bit)) +#define clear_bit(bit, v) (v) &= ~(1U<<(bit)) +#define test_bit(bit, v) ((v) & (1U<<(bit))) struct idtree_layer { uint32_t bitmap; @@ -122,7 +122,7 @@ restart: /* no space available go back to previous layer. */ l++; oid = id; - id = (id | ((1 << (IDTREE_BITS*l))-1)) + 1; + id = (id | ((1U << (IDTREE_BITS*l))-1)) + 1; /* if already at the top layer, we need to grow */ if (!(p = pa[l])) { diff --git a/ccan/ilog/ilog.h b/ccan/ilog/ilog.h index 9adbb824..32702b17 100644 --- a/ccan/ilog/ilog.h +++ b/ccan/ilog/ilog.h @@ -120,7 +120,10 @@ int ilog64_nz(uint64_t _v) CONST_FUNCTION; #endif #ifdef builtin_ilog32_nz -#define ilog32(_v) (builtin_ilog32_nz(_v)&-!!(_v)) +/* This used to be builtin_ilog32_nz(_v)&-!!(_v), which means it zeroes out + * the undefined builtin_ilog32_nz(0) return. But clang UndefinedBehaviorSantizer + * complains, so do the branch: */ +#define ilog32(_v) ((_v) ? builtin_ilog32_nz(_v) : 0) #define ilog32_nz(_v) builtin_ilog32_nz(_v) #else #define ilog32_nz(_v) ilog32(_v) @@ -128,7 +131,7 @@ int ilog64_nz(uint64_t _v) CONST_FUNCTION; #endif /* builtin_ilog32_nz */ #ifdef builtin_ilog64_nz -#define ilog64(_v) (builtin_ilog64_nz(_v)&-!!(_v)) +#define ilog32(_v) ((_v) ? builtin_ilog32_nz(_v) : 0) #define ilog64_nz(_v) builtin_ilog64_nz(_v) #else #define ilog64_nz(_v) ilog64(_v) diff --git a/ccan/io/fdpass/_info b/ccan/io/fdpass/_info index ba09025a..0b10e8a8 100644 --- a/ccan/io/fdpass/_info +++ b/ccan/io/fdpass/_info @@ -32,12 +32,20 @@ * read_more, buf); * } * + * // Clean up allocation so -fsanitize=address doesn't see leak! + * static void free_buf(struct io_conn *c, struct buf *buf) + * { + * free(buf); + * } + * * // Child has received fd, start reading loop. * static struct io_plan *got_infd(struct io_conn *conn, int *infd) * { * struct buf *buf = calloc(1, sizeof(*buf)); + * struct io_conn *new_conn; * - * io_new_conn(NULL, *infd, read_more, buf); + * new_conn = io_new_conn(NULL, *infd, read_more, buf); + * io_set_finish(new_conn, free_buf, buf); * return io_close(conn); * } * // Child is receiving the fd to read into. diff --git a/ccan/io/io.c b/ccan/io/io.c index 36dcb81e..12df06d8 100644 --- a/ccan/io/io.c +++ b/ccan/io/io.c @@ -529,6 +529,18 @@ bool io_plan_out_started(const struct io_conn *conn) return conn->plan[IO_OUT].status == IO_POLLING_STARTED; } +/* Despite being a TCP expert, I missed the full extent of this + * problem. The legendary ZmnSCPxj implemented it (with the URL + * pointing to the explanation), and I imitate that here. */ +struct io_plan *io_sock_shutdown(struct io_conn *conn) +{ + if (shutdown(io_conn_fd(conn), SHUT_WR) != 0) + return io_close(conn); + + /* And leave unset .*/ + return &conn->plan[IO_IN]; +} + bool io_flush_sync(struct io_conn *conn) { struct io_plan *plan = &conn->plan[IO_OUT]; diff --git a/ccan/io/io.h b/ccan/io/io.h index e6905fb9..eeb5e36e 100644 --- a/ccan/io/io.h +++ b/ccan/io/io.h @@ -389,6 +389,39 @@ struct io_plan *io_out_always_(struct io_conn *conn, void *), void *arg); +/** + * io_sock_shutdown - start socket close process (flushes TCP sockets). + * @conn: the connection the plan is for + * + * Simply closing a TCP socket can lose data; unfortunately you should + * shutdown(SHUT_WR) and wait for the other side to see this and close. + * Of course, you also need to set a timer, in case it doesn't (you may + * already have some responsiveness timer, of course). + * + * On error, is equivalent to io_close(). + * + * Example: + * #include + * + * // Timer infra needs wrapper to contain extra data. + * struct timeout_timer { + * struct timer t; + * struct io_conn *conn; + * }; + * static struct timers timers; + * + * static struct io_plan *flush_and_close(struct io_conn *conn) + * { + * struct timeout_timer *timeout; + * // Freed if conn closes normally. + * timeout = tal(conn, struct timeout_timer); + * timeout->conn = conn; + * timer_addrel(&timers, &timeout->t, time_from_sec(5)); + * return io_sock_shutdown(conn); + * } + */ +struct io_plan *io_sock_shutdown(struct io_conn *conn); + /** * io_connect - create an asynchronous connection to a listening socket. * @conn: the connection that plan is for. @@ -781,4 +814,13 @@ struct timemono (*io_time_override(struct timemono (*now)(void)))(void); */ int (*io_poll_override(int (*poll)(struct pollfd *fds, nfds_t nfds, int timeout)))(struct pollfd *, nfds_t, int); +/** + * io_have_fd - do we own this file descriptor? + * @fd: the file descriptor. + * @listener: if non-NULL, set to true if it's a listening socket (io_listener). + * + * Returns NULL if we don't own it, otherwise a struct io_conn * or struct io_listener *. + */ +const void *io_have_fd(int fd, bool *listener); + #endif /* CCAN_IO_H */ diff --git a/ccan/io/poll.c b/ccan/io/poll.c index 17f93845..634f83d2 100644 --- a/ccan/io/poll.c +++ b/ccan/io/poll.c @@ -121,7 +121,9 @@ bool add_listener(struct io_listener *l) static int find_always(const struct io_plan *plan) { - for (size_t i = 0; i < num_always; i++) + size_t i = 0; + + for (i = 0; i < num_always; i++) if (always[i] == plan) return i; return -1; @@ -287,8 +289,10 @@ static bool *exclusive(struct io_plan *plan) /* For simplicity, we do one always at a time */ static bool handle_always(void) { + int i; + /* Backwards is simple easier to remove entries */ - for (int i = num_always - 1; i >= 0; i--) { + for (i = num_always - 1; i >= 0; i--) { struct io_plan *plan = always[i]; if (num_exclusive && !*exclusive(plan)) @@ -323,10 +327,12 @@ bool backend_set_exclusive(struct io_plan *plan, bool excl) * else that we manipulate events. */ static void exclude_pollfds(void) { + size_t i; + if (num_exclusive == 0) return; - for (size_t i = 0; i < num_fds; i++) { + for (i = 0; i < num_fds; i++) { struct pollfd *pfd = &pollfds[fds[i]->backend_info]; if (!fds[i]->exclusive[IO_IN]) @@ -343,10 +349,12 @@ static void exclude_pollfds(void) static void restore_pollfds(void) { + size_t i; + if (num_exclusive == 0) return; - for (size_t i = 0; i < num_fds; i++) { + for (i = 0; i < num_fds; i++) { struct pollfd *pfd = &pollfds[fds[i]->backend_info]; if (fds[i]->listener) { @@ -456,3 +464,15 @@ void *io_loop(struct timers *timers, struct timer **expired) return ret; } + +const void *io_have_fd(int fd, bool *listener) +{ + for (size_t i = 0; i < num_fds; i++) { + if (fds[i]->fd != fd) + continue; + if (listener) + *listener = fds[i]->listener; + return fds[i]; + } + return NULL; +} diff --git a/ccan/json/json.h b/ccan/json/json.h index ed5255e6..dccb58aa 100644 --- a/ccan/json/json.h +++ b/ccan/json/json.h @@ -79,7 +79,7 @@ bool json_validate (const char *json); /*** Lookup and traversal ***/ JsonNode *json_find_element (JsonNode *array, int index); -JsonNode *json_find_member (JsonNode *object, const char *key); +JsonNode *json_find_member (JsonNode *object, const char *name); JsonNode *json_first_child (const JsonNode *node); diff --git a/ccan/json_escape/json_escape.c b/ccan/json_escape/json_escape.c index ed4bfab9..daa14abe 100644 --- a/ccan/json_escape/json_escape.c +++ b/ccan/json_escape/json_escape.c @@ -21,7 +21,8 @@ bool json_escape_eq(const struct json_escape *a, const struct json_escape *b) bool json_escape_needed(const char *str, size_t len) { - for (size_t i = 0; i < len; i++) { + size_t i; + for (i = 0; i < len; i++) { if ((unsigned)str[i] < ' ' || str[i] == 127 || str[i] == '"' diff --git a/ccan/json_out/json_out.c b/ccan/json_out/json_out.c index 87deaca8..53837e67 100644 --- a/ccan/json_out/json_out.c +++ b/ccan/json_out/json_out.c @@ -337,11 +337,12 @@ char *json_out_direct(struct json_out *jout, size_t len) return p; } -void json_out_finished(const struct json_out *jout) +void json_out_finished(struct json_out *jout) { #ifdef CCAN_JSON_OUT_DEBUG assert(tal_count(jout->wrapping) == 0); #endif + jout->empty = true; } const char *json_out_contents(const struct json_out *jout, size_t *len) diff --git a/ccan/json_out/json_out.h b/ccan/json_out/json_out.h index 2911ff24..da8b4ffa 100644 --- a/ccan/json_out/json_out.h +++ b/ccan/json_out/json_out.h @@ -177,9 +177,12 @@ bool json_out_add_splice(struct json_out *jout, * @jout: the json_out object written to. * * This simply causes internal assertions that all arrays and objects are - * finished. It needs CCAN_JSON_OUT_DEBUG defined to have any effect. + * finished. If CCAN_JSON_OUT_DEBUG is defined, it does sanity checks. + * + * This also resets the empty flag, so there will be no comma added if + * another JSON object is written. */ -void json_out_finished(const struct json_out *jout); +void json_out_finished(struct json_out *jout); /** * json_out_contents - read contents from json_out stream. diff --git a/ccan/mem/mem.h b/ccan/mem/mem.h index 19f69c03..20286dcb 100644 --- a/ccan/mem/mem.h +++ b/ccan/mem/mem.h @@ -104,7 +104,7 @@ void *memcchr(void const *data, int c, size_t data_len); PURE_FUNCTION static inline bool memeq(const void *a, size_t al, const void *b, size_t bl) { - return al == bl && !memcmp(a, b, bl); + return al == bl && (al == 0 || !memcmp(a, b, bl)); } /** diff --git a/ccan/membuf/_info b/ccan/membuf/_info index bdcbce2b..a859318c 100644 --- a/ccan/membuf/_info +++ b/ccan/membuf/_info @@ -26,13 +26,16 @@ * * membuf_init(&charbuf, malloc(10), 10, membuf_realloc); * - * for (int i = 1; i < argc; i++) - * strcpy(membuf_add(&charbuf, strlen(argv[i])), argv[i]); + * for (int i = 1; i < argc; i++) { + * size_t len = strlen(argv[i]); + * memcpy(membuf_add(&charbuf, len), argv[i], len); + * } * * // This is dumb, we could do all at once, but shows technique. * while (membuf_num_elems(&charbuf) > 0) * printf("%c", *(char *)membuf_consume(&charbuf, 1)); * printf("\n"); + * free(membuf_cleanup(&charbuf)); * return 0; * } */ diff --git a/ccan/net/net.c b/ccan/net/net.c index 11c6b670..f57b8dd6 100644 --- a/ccan/net/net.c +++ b/ccan/net/net.c @@ -75,7 +75,7 @@ close: } -int net_connect_async(const struct addrinfo *addrinfo, struct pollfd pfds[2]) +int net_connect_async(const struct addrinfo *addrinfo, struct pollfd *pfds) { const struct addrinfo *addr[2] = { NULL, NULL }; unsigned int i; @@ -122,7 +122,7 @@ int net_connect_async(const struct addrinfo *addrinfo, struct pollfd pfds[2]) return -1; } -void net_connect_abort(struct pollfd pfds[2]) +void net_connect_abort(struct pollfd *pfds) { unsigned int i; @@ -133,7 +133,7 @@ void net_connect_abort(struct pollfd pfds[2]) } } -int net_connect_complete(struct pollfd pfds[2]) +int net_connect_complete(struct pollfd *pfds) { unsigned int i; diff --git a/ccan/objset/_info b/ccan/objset/_info index 967764e7..116c2596 100644 --- a/ccan/objset/_info +++ b/ccan/objset/_info @@ -39,6 +39,8 @@ * printf("%i,", i); * printf("\n"); * } + * // Keep -fsanitize=address leak detection happy. + * objset_clear(&args); * return 0; * } * // Given "a b c" outputs No arguments start with -. diff --git a/ccan/objset/objset.h b/ccan/objset/objset.h index 9444d48c..03ec00f3 100644 --- a/ccan/objset/objset.h +++ b/ccan/objset/objset.h @@ -132,7 +132,7 @@ static inline bool objset_empty_(const struct objset_h *set) /** * objset_iter - iterator reference. * - * This is valid for a particular set as long as the contents remain unchaged, + * This is valid for a particular set as long as the contents remain unchanged, * otherwise the effect is undefined. */ struct objset_iter { diff --git a/ccan/opt/helpers.c b/ccan/opt/helpers.c index 118e5436..df7ee6bb 100644 --- a/ccan/opt/helpers.c +++ b/ccan/opt/helpers.c @@ -138,10 +138,11 @@ char *opt_set_floatval(const char *arg, float *f) return NULL; } -void opt_show_floatval(char buf[OPT_SHOW_LEN], const float *f) +bool opt_show_floatval(char *buf, size_t len, const float *f) { double d = *f; - opt_show_doubleval(buf, &d); + opt_show_doubleval(buf, len, &d); + return true; } char *opt_set_doubleval(const char *arg, double *d) @@ -160,9 +161,10 @@ char *opt_set_doubleval(const char *arg, double *d) return NULL; } -void opt_show_doubleval(char buf[OPT_SHOW_LEN], const double *d) +bool opt_show_doubleval(char *buf, size_t len, const double *d) { - snprintf(buf, OPT_SHOW_LEN, "%f", *d); + snprintf(buf, len, "%f", *d); + return true; } char *opt_inc_intval(int *i) @@ -196,52 +198,60 @@ char *opt_usage_and_exit(const char *extra) exit(0); } -void opt_show_bool(char buf[OPT_SHOW_LEN], const bool *b) +bool opt_show_bool(char *buf, size_t len, const bool *b) { - strncpy(buf, *b ? "true" : "false", OPT_SHOW_LEN); + strncpy(buf, *b ? "true" : "false", len); + return true; } -void opt_show_invbool(char buf[OPT_SHOW_LEN], const bool *b) +bool opt_show_invbool(char *buf, size_t len, const bool *b) { - strncpy(buf, *b ? "false" : "true", OPT_SHOW_LEN); + strncpy(buf, *b ? "false" : "true", len); + return true; } -void opt_show_charp(char buf[OPT_SHOW_LEN], char *const *p) +bool opt_show_charp(char *buf, size_t len, char *const *p) { - if (*p){ - size_t len = strlen(*p); + if (*p) { + size_t plen = strlen(*p); + if (len < 2) + return false; buf[0] = '"'; - if (len > OPT_SHOW_LEN - 2) - len = OPT_SHOW_LEN - 2; - strncpy(buf+1, *p, len); - buf[1+len] = '"'; - if (len < OPT_SHOW_LEN - 2) - buf[2+len] = '\0'; - } - else { - strncpy(buf, "(nil)", OPT_SHOW_LEN); + if (plen > len - 2) + plen = len - 2; + strncpy(buf+1, *p, plen); + buf[1+plen] = '"'; + if (plen < len - 2) + buf[2+plen] = '\0'; + return true; + } else { + return false; } } /* Show an integer value, various forms. */ -void opt_show_intval(char buf[OPT_SHOW_LEN], const int *i) +bool opt_show_intval(char *buf, size_t len, const int *i) { - snprintf(buf, OPT_SHOW_LEN, "%i", *i); + snprintf(buf, len, "%i", *i); + return true; } -void opt_show_uintval(char buf[OPT_SHOW_LEN], const unsigned int *ui) +bool opt_show_uintval(char *buf, size_t len, const unsigned int *ui) { - snprintf(buf, OPT_SHOW_LEN, "%u", *ui); + snprintf(buf, len, "%u", *ui); + return true; } -void opt_show_longval(char buf[OPT_SHOW_LEN], const long *l) +bool opt_show_longval(char *buf, size_t len, const long *l) { - snprintf(buf, OPT_SHOW_LEN, "%li", *l); + snprintf(buf, len, "%li", *l); + return true; } -void opt_show_ulongval(char buf[OPT_SHOW_LEN], const unsigned long *ul) +bool opt_show_ulongval(char *buf, size_t len, const unsigned long *ul) { - snprintf(buf, OPT_SHOW_LEN, "%lu", *ul); + snprintf(buf, len, "%lu", *ul); + return true; } /* a helper function that multiplies out an argument's kMGTPE suffix in the @@ -447,14 +457,14 @@ char * opt_set_uintval_si(const char *arg, unsigned int *u) are separate but essentially identical functions for signed and unsigned values, so that unsigned values greater than LLONG_MAX get suffixes. */ -static void show_llong_with_suffix(char buf[OPT_SHOW_LEN], long long ll, - const long long base) +static void show_llong_with_suffix(char *buf, size_t len, long long ll, + const long long base) { const char *suffixes = "kMGTPE"; int i; if (ll == 0){ /*zero is special because everything divides it (you'd get "0E")*/ - snprintf(buf, OPT_SHOW_LEN, "0"); + snprintf(buf, len, "0"); return; } for (i = 0; i < strlen(suffixes); i++){ @@ -464,19 +474,20 @@ static void show_llong_with_suffix(char buf[OPT_SHOW_LEN], long long ll, ll = tmp; } if (i == 0) - snprintf(buf, OPT_SHOW_LEN, "%"PRId64, (int64_t)ll); + snprintf(buf, len, "%"PRId64, (int64_t)ll); else - snprintf(buf, OPT_SHOW_LEN, "%"PRId64"%c", (int64_t)ll, suffixes[i - 1]); + snprintf(buf, len, "%"PRId64"%c", (int64_t)ll, suffixes[i - 1]); } -static void show_ullong_with_suffix(char buf[OPT_SHOW_LEN], unsigned long long ull, +static void show_ullong_with_suffix(char *buf, size_t len, + unsigned long long ull, const unsigned base) { const char *suffixes = "kMGTPE"; int i; if (ull == 0){ /*zero is special because everything divides it (you'd get "0E")*/ - snprintf(buf, OPT_SHOW_LEN, "0"); + snprintf(buf, len, "0"); return; } for (i = 0; i < strlen(suffixes); i++){ @@ -486,72 +497,84 @@ static void show_ullong_with_suffix(char buf[OPT_SHOW_LEN], unsigned long long u ull = tmp; } if (i == 0) - snprintf(buf, OPT_SHOW_LEN, "%"PRIu64, (uint64_t)ull); + snprintf(buf, len, "%"PRIu64, (uint64_t)ull); else - snprintf(buf, OPT_SHOW_LEN, "%"PRIu64"%c", (uint64_t)ull, suffixes[i - 1]); + snprintf(buf, len, "%"PRIu64"%c", (uint64_t)ull, suffixes[i - 1]); } /* _bi, signed */ -void opt_show_intval_bi(char buf[OPT_SHOW_LEN], const int *x) +bool opt_show_intval_bi(char *buf, size_t len, const int *x) { - show_llong_with_suffix(buf, *x, 1024); + show_llong_with_suffix(buf, len, *x, 1024); + return true; } -void opt_show_longval_bi(char buf[OPT_SHOW_LEN], const long *x) +bool opt_show_longval_bi(char *buf, size_t len, const long *x) { - show_llong_with_suffix(buf, *x, 1024); + show_llong_with_suffix(buf, len, *x, 1024); + return true; } -void opt_show_longlongval_bi(char buf[OPT_SHOW_LEN], const long long *x) +bool opt_show_longlongval_bi(char *buf, size_t len, const long long *x) { - show_llong_with_suffix(buf, *x, 1024); + show_llong_with_suffix(buf, len, *x, 1024); + return true; } /* _bi, unsigned */ -void opt_show_uintval_bi(char buf[OPT_SHOW_LEN], const unsigned int *x) +bool opt_show_uintval_bi(char *buf, size_t len, const unsigned int *x) { - show_ullong_with_suffix(buf, (unsigned long long) *x, 1024); + show_ullong_with_suffix(buf, len, (unsigned long long) *x, 1024); + return true; } -void opt_show_ulongval_bi(char buf[OPT_SHOW_LEN], const unsigned long *x) +bool opt_show_ulongval_bi(char *buf, size_t len, const unsigned long *x) { - show_ullong_with_suffix(buf, (unsigned long long) *x, 1024); + show_ullong_with_suffix(buf, len, (unsigned long long) *x, 1024); + return true; } -void opt_show_ulonglongval_bi(char buf[OPT_SHOW_LEN], const unsigned long long *x) +bool opt_show_ulonglongval_bi(char *buf, size_t len, const unsigned long long *x) { - show_ullong_with_suffix(buf, (unsigned long long) *x, 1024); + show_ullong_with_suffix(buf, len, (unsigned long long) *x, 1024); + return true; } /* _si, signed */ -void opt_show_intval_si(char buf[OPT_SHOW_LEN], const int *x) +bool opt_show_intval_si(char *buf, size_t len, const int *x) { - show_llong_with_suffix(buf, (long long) *x, 1000); + show_llong_with_suffix(buf, len, (long long) *x, 1000); + return true; } -void opt_show_longval_si(char buf[OPT_SHOW_LEN], const long *x) +bool opt_show_longval_si(char *buf, size_t len, const long *x) { - show_llong_with_suffix(buf, (long long) *x, 1000); + show_llong_with_suffix(buf, len, (long long) *x, 1000); + return true; } -void opt_show_longlongval_si(char buf[OPT_SHOW_LEN], const long long *x) +bool opt_show_longlongval_si(char *buf, size_t len, const long long *x) { - show_llong_with_suffix(buf, *x, 1000); + show_llong_with_suffix(buf, len, *x, 1000); + return true; } /* _si, unsigned */ -void opt_show_uintval_si(char buf[OPT_SHOW_LEN], const unsigned int *x) +bool opt_show_uintval_si(char *buf, size_t len, const unsigned int *x) { - show_ullong_with_suffix(buf, (unsigned long long) *x, 1000); + show_ullong_with_suffix(buf, len, (unsigned long long) *x, 1000); + return true; } -void opt_show_ulongval_si(char buf[OPT_SHOW_LEN], const unsigned long *x) +bool opt_show_ulongval_si(char *buf, size_t len, const unsigned long *x) { - show_ullong_with_suffix(buf, (unsigned long long) *x, 1000); + show_ullong_with_suffix(buf, len, (unsigned long long) *x, 1000); + return true; } -void opt_show_ulonglongval_si(char buf[OPT_SHOW_LEN], const unsigned long long *x) +bool opt_show_ulonglongval_si(char *buf, size_t len, const unsigned long long *x) { - show_ullong_with_suffix(buf, (unsigned long long) *x, 1000); + show_ullong_with_suffix(buf, len, (unsigned long long) *x, 1000); + return true; } diff --git a/ccan/opt/opt.c b/ccan/opt/opt.c index d376a598..9149374c 100644 --- a/ccan/opt/opt.c +++ b/ccan/opt/opt.c @@ -34,7 +34,7 @@ static const char *next_name(const char *names, unsigned *len) static const char *first_opt(unsigned *i, unsigned *len) { for (*i = 0; *i < opt_count; (*i)++) { - if (opt_table[*i].type == OPT_SUBTABLE) + if (opt_table[*i].type & OPT_SUBTABLE) continue; return first_name(opt_table[*i].names, len); } @@ -44,7 +44,7 @@ static const char *first_opt(unsigned *i, unsigned *len) static const char *next_opt(const char *p, unsigned *i, unsigned *len) { for (; *i < opt_count; (*i)++) { - if (opt_table[*i].type == OPT_SUBTABLE) + if (opt_table[*i].type & OPT_SUBTABLE) continue; if (!p) return first_name(opt_table[*i].names, len); @@ -114,10 +114,11 @@ static void check_opt(const struct opt_table *entry) { const char *p; unsigned len; + enum opt_type type = entry->type & (OPT_USER_MIN-1); - if (entry->type != OPT_HASARG && entry->type != OPT_NOARG - && entry->type != (OPT_EARLY|OPT_HASARG) - && entry->type != (OPT_EARLY|OPT_NOARG)) + if (type != OPT_HASARG && type != OPT_NOARG + && type != (OPT_EARLY|OPT_HASARG) + && type != (OPT_EARLY|OPT_NOARG)) failmsg("Option %s: unknown entry type %u", entry->names, entry->type); @@ -161,7 +162,7 @@ static void add_opt(const struct opt_table *entry) void _opt_register(const char *names, enum opt_type type, char *(*cb)(void *arg), char *(*cb_arg)(const char *optarg, void *arg), - void (*show)(char buf[OPT_SHOW_LEN], const void *arg), + bool (*show)(char *buf, size_t len, const void *arg), const void *arg, const char *desc) { struct opt_table opt; @@ -181,7 +182,7 @@ bool opt_unregister(const char *names) int found = -1, i; for (i = 0; i < opt_count; i++) { - if (opt_table[i].type == OPT_SUBTABLE) + if (opt_table[i].type & OPT_SUBTABLE) continue; if (strcmp(opt_table[i].names, names) == 0) found = i; @@ -203,7 +204,7 @@ void opt_register_table(const struct opt_table entry[], const char *desc) add_opt(&heading); } for (i = 0; entry[i].type != OPT_END; i++) { - if (entry[i].type == OPT_SUBTABLE) + if (entry[i].type & OPT_SUBTABLE) opt_register_table(subtable_of(&entry[i]), entry[i].desc); else { diff --git a/ccan/opt/opt.h b/ccan/opt/opt.h index 6f4b9dda..e0331be2 100644 --- a/ccan/opt/opt.h +++ b/ccan/opt/opt.h @@ -47,10 +47,11 @@ struct opt_table; * where "type" is the type of the @arg argument. The first argument to the * @cb is the argument found on the commandline. * - * Similarly, if @show is not NULL, it should be of type "void *show(char *, - * const type *)". It should write up to OPT_SHOW_LEN bytes into the first - * argument; unless it uses the entire OPT_SHOW_LEN bytes it should - * nul-terminate that buffer. + * Similarly, if @show is not NULL, it should be of type "bool show(char *, + * size_t len, const type *)". If there is no default, it should return false, + * otherwise it should write up to len bytes into the first argument and + * return true; unless it uses the entire len bytes it should nul-terminate that + * buffer. * * Any number of equivalent short or long options can be listed in @names, * separated by '|'. Short options are a single hyphen followed by a single @@ -429,40 +430,38 @@ void opt_usage_exit_fail(const char *msg, ...) NORETURN; */ extern const char opt_hidden[]; -/* Maximum length of arg to show in opt_usage */ -#define OPT_SHOW_LEN 80 - /* Standard helpers. You can write your own: */ /* Sets the @b to true. */ char *opt_set_bool(bool *b); /* Sets @b based on arg: (yes/no/true/false). */ char *opt_set_bool_arg(const char *arg, bool *b); -void opt_show_bool(char buf[OPT_SHOW_LEN], const bool *b); +bool opt_show_bool(char *buf, size_t len, const bool *b); /* The inverse */ char *opt_set_invbool(bool *b); -void opt_show_invbool(char buf[OPT_SHOW_LEN], const bool *b); +bool opt_show_invbool(char *buf, size_t len, const bool *b); /* Sets @b based on !arg: (yes/no/true/false). */ char *opt_set_invbool_arg(const char *arg, bool *b); /* Set a char *. */ char *opt_set_charp(const char *arg, char **p); -void opt_show_charp(char buf[OPT_SHOW_LEN], char *const *p); +/* If *p is NULL, this returns false (i.e. doesn't show a default) */ +bool opt_show_charp(char *buf, size_t len, char *const *p); /* Set an integer value, various forms. Sets to 1 on arg == NULL. */ char *opt_set_intval(const char *arg, int *i); -void opt_show_intval(char buf[OPT_SHOW_LEN], const int *i); +bool opt_show_intval(char *buf, size_t len, const int *i); char *opt_set_uintval(const char *arg, unsigned int *ui); -void opt_show_uintval(char buf[OPT_SHOW_LEN], const unsigned int *ui); +bool opt_show_uintval(char *buf, size_t len, const unsigned int *ui); char *opt_set_longval(const char *arg, long *l); -void opt_show_longval(char buf[OPT_SHOW_LEN], const long *l); +bool opt_show_longval(char *buf, size_t len, const long *l); char *opt_set_ulongval(const char *arg, unsigned long *ul); -void opt_show_ulongval(char buf[OPT_SHOW_LEN], const unsigned long *ul); +bool opt_show_ulongval(char *buf, size_t len, const unsigned long *ul); /* Set an floating point value, various forms. */ char *opt_set_floatval(const char *arg, float *f); -void opt_show_floatval(char buf[OPT_SHOW_LEN], const float *f); +bool opt_show_floatval(char *buf, size_t len, const float *f); char *opt_set_doubleval(const char *arg, double *d); -void opt_show_doubleval(char buf[OPT_SHOW_LEN], const double *d); +bool opt_show_doubleval(char *buf, size_t len, const double *d); /* the following setting functions accept k, M, G, T, P, or E suffixes, which multiplies the numeric value by the corresponding power of 1000 or 1024 @@ -482,19 +481,19 @@ char *opt_set_ulonglongval_bi(const char *arg, unsigned long long *ll); char *opt_set_ulonglongval_si(const char *arg, unsigned long long *ll); -void opt_show_intval_bi(char buf[OPT_SHOW_LEN], const int *x); -void opt_show_longval_bi(char buf[OPT_SHOW_LEN], const long *x); -void opt_show_longlongval_bi(char buf[OPT_SHOW_LEN], const long long *x); -void opt_show_uintval_bi(char buf[OPT_SHOW_LEN], const unsigned int *x); -void opt_show_ulongval_bi(char buf[OPT_SHOW_LEN], const unsigned long *x); -void opt_show_ulonglongval_bi(char buf[OPT_SHOW_LEN], const unsigned long long *x); +bool opt_show_intval_bi(char *buf, size_t len, const int *x); +bool opt_show_longval_bi(char *buf, size_t len, const long *x); +bool opt_show_longlongval_bi(char *buf, size_t len, const long long *x); +bool opt_show_uintval_bi(char *buf, size_t len, const unsigned int *x); +bool opt_show_ulongval_bi(char *buf, size_t len, const unsigned long *x); +bool opt_show_ulonglongval_bi(char *buf, size_t len, const unsigned long long *x); -void opt_show_intval_si(char buf[OPT_SHOW_LEN], const int *x); -void opt_show_longval_si(char buf[OPT_SHOW_LEN], const long *x); -void opt_show_longlongval_si(char buf[OPT_SHOW_LEN], const long long *x); -void opt_show_uintval_si(char buf[OPT_SHOW_LEN], const unsigned int *x); -void opt_show_ulongval_si(char buf[OPT_SHOW_LEN], const unsigned long *x); -void opt_show_ulonglongval_si(char buf[OPT_SHOW_LEN], const unsigned long long *x); +bool opt_show_intval_si(char *buf, size_t len, const int *x); +bool opt_show_longval_si(char *buf, size_t len, const long *x); +bool opt_show_longlongval_si(char *buf, size_t len, const long long *x); +bool opt_show_uintval_si(char *buf, size_t len, const unsigned int *x); +bool opt_show_ulongval_si(char *buf, size_t len, const unsigned long *x); +bool opt_show_ulonglongval_si(char *buf, size_t len, const unsigned long long *x); @@ -509,6 +508,30 @@ char *opt_version_and_exit(const char *version); /* Display usage string to stdout, exit(0). */ char *opt_usage_and_exit(const char *extra); +/** + * opt_find_long: low-level access to the parser + * @arg: string of form 'arg' or 'arg=val'. + * @optarg: set to `val` of present in arg, otherwise NULL. Can be NULL. + * + * Returns NULL if option is unknown. Sets *@optarg to NULL if + * there's no '='. + */ +struct opt_table *opt_find_long(const char *arg, const char **optarg); + +/** + * opt_find_short: low-level access to the parser + * @arg: character representing short option + * + * Returns NULL if option is unknown. + */ +struct opt_table *opt_find_short(char arg); + +/* opt_type bits reserved for users to play with (ignored!). + * You can set bits in type e.g. (1<type & OPT_NOARG) { if (optarg) return parse_err(errlog, argv[0], o, len, "doesn't allow an argument"); - if ((opt_table[i].type & OPT_EARLY) == is_early) - problem = opt_table[i].cb(opt_table[i].u.arg); + if ((ot->type & OPT_EARLY) == is_early) + problem = ot->cb(ot->u.arg); } else { if (!optarg) { /* Swallow any short options as optarg, eg -afile */ @@ -117,9 +158,8 @@ int parse_one(int *argc, char *argv[], enum opt_type is_early, unsigned *offset, if (!optarg) return parse_err(errlog, argv[0], o, len, "requires an argument"); - if ((opt_table[i].type & OPT_EARLY) == is_early) - problem = opt_table[i].cb_arg(optarg, - opt_table[i].u.arg); + if ((ot->type & OPT_EARLY) == is_early) + problem = ot->cb_arg(optarg, ot->u.arg); } if (problem) { diff --git a/ccan/opt/test/run-add_desc.c b/ccan/opt/test/run-add_desc.c index b559c7f7..03e6986d 100644 --- a/ccan/opt/test/run-add_desc.c +++ b/ccan/opt/test/run-add_desc.c @@ -4,15 +4,24 @@ #include #include -static void show_10(char buf[OPT_SHOW_LEN], const void *arg UNNEEDED) +static bool show_10(char *buf, size_t len, const void *arg UNNEEDED) { memset(buf, 'X', 10); buf[10] = '\0'; + return true; } -static void show_max(char buf[OPT_SHOW_LEN], const void *arg UNNEEDED) +static bool show_10_false(char *buf, size_t len, const void *arg UNNEEDED) +{ + memset(buf, 'X', 10); + buf[10] = '\0'; + return false; +} + +static bool show_max(char *buf, size_t len, const void *arg UNNEEDED) { memset(buf, 'X', OPT_SHOW_LEN); + return true; } /* Test add_desc helper. */ @@ -22,7 +31,7 @@ int main(void) char *ret; size_t len, max; - plan_tests(30); + plan_tests(32); opt.show = NULL; opt.names = "01234"; @@ -113,6 +122,14 @@ int main(void) " (default: XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX...)\n") == 0); free(ret); len = max = 0; + /* With show function which fails doesn't print. */ + opt.show = show_10_false; + ret = add_desc(NULL, &len, &max, 7, 41, &opt); + ok1(len < max); + ret[len] = '\0'; + ok1(strcmp(ret, "01234 0123456789 0\n") == 0); + free(ret); len = max = 0; + /* With added " ". Fits, just. */ opt.show = NULL; opt.type = OPT_HASARG; diff --git a/ccan/opt/test/run-correct-reporting.c b/ccan/opt/test/run-correct-reporting.c index 8534f291..0c4f6c86 100644 --- a/ccan/opt/test/run-correct-reporting.c +++ b/ccan/opt/test/run-correct-reporting.c @@ -10,7 +10,7 @@ int main(int argc, char *argv[]) { - plan_tests(12); + plan_tests(14); /* --aaa without args. */ opt_register_arg("-a|--aaa", test_arg, NULL, "aaa", ""); @@ -42,6 +42,10 @@ int main(int argc, char *argv[]) free(err_output); err_output = NULL; + opt_register_noarg("-d", test_noarg, NULL, ""); + ok1(!parse_args(&argc, &argv, "-dc", NULL)); + ok1(strstr(err_output, ": -c: requires an argument")); + /* parse_args allocates argv */ free(argv); return exit_status(); diff --git a/ccan/opt/test/run-helpers.c b/ccan/opt/test/run-helpers.c index 0a08a85f..9aa41fe8 100644 --- a/ccan/opt/test/run-helpers.c +++ b/ccan/opt/test/run-helpers.c @@ -476,26 +476,26 @@ int main(int argc, char *argv[]) char buf[OPT_SHOW_LEN+2] = { 0 }; buf[OPT_SHOW_LEN] = '!'; i = -77; - opt_show_intval_bi(buf, &i); + opt_show_intval_bi(buf, OPT_SHOW_LEN, &i); ok1(strcmp(buf, "-77") == 0); i = 0; - opt_show_intval_bi(buf, &i); + opt_show_intval_bi(buf, OPT_SHOW_LEN, &i); ok1(strcmp(buf, "0") == 0); ok1(buf[OPT_SHOW_LEN] == '!'); i = 77; - opt_show_intval_bi(buf, &i); + opt_show_intval_bi(buf, OPT_SHOW_LEN, &i); ok1(strcmp(buf, "77") == 0); ok1(buf[OPT_SHOW_LEN] == '!'); i = -1234 * k; - opt_show_intval_bi(buf, &i); + opt_show_intval_bi(buf, OPT_SHOW_LEN, &i); ok1(strcmp(buf, "-1234k") == 0); ok1(buf[OPT_SHOW_LEN] == '!'); i = 500 * M; - opt_show_intval_bi(buf, &i); + opt_show_intval_bi(buf, OPT_SHOW_LEN, &i); ok1(strcmp(buf, "500M") == 0); ok1(buf[OPT_SHOW_LEN] == '!'); i = 1024 * M; - opt_show_intval_bi(buf, &i); + opt_show_intval_bi(buf, OPT_SHOW_LEN, &i); ok1(strcmp(buf, "1G") == 0); ok1(buf[OPT_SHOW_LEN] == '!'); } @@ -506,27 +506,27 @@ int main(int argc, char *argv[]) char buf[OPT_SHOW_LEN+2] = { 0 }; buf[OPT_SHOW_LEN] = '!'; i = -77; - opt_show_longval_bi(buf, &i); + opt_show_longval_bi(buf, OPT_SHOW_LEN, &i); ok1(strcmp(buf, "-77") == 0); ok1(buf[OPT_SHOW_LEN] == '!'); i = 77; - opt_show_longval_bi(buf, &i); + opt_show_longval_bi(buf, OPT_SHOW_LEN, &i); ok1(strcmp(buf, "77") == 0); ok1(buf[OPT_SHOW_LEN] == '!'); i = -1 * k; - opt_show_longval_bi(buf, &i); + opt_show_longval_bi(buf, OPT_SHOW_LEN, &i); ok1(strcmp(buf, "-1k") == 0); ok1(buf[OPT_SHOW_LEN] == '!'); i = 500 * M; - opt_show_longval_bi(buf, &i); + opt_show_longval_bi(buf, OPT_SHOW_LEN, &i); ok1(strcmp(buf, "500M") == 0); ok1(buf[OPT_SHOW_LEN] == '!'); i = 1024 * M; - opt_show_longval_bi(buf, &i); + opt_show_longval_bi(buf, OPT_SHOW_LEN, &i); ok1(strcmp(buf, "1G") == 0); ok1(buf[OPT_SHOW_LEN] == '!'); i = 0; - opt_show_longval_bi(buf, &i); + opt_show_longval_bi(buf, OPT_SHOW_LEN, &i); ok1(strcmp(buf, "0") == 0); ok1(buf[OPT_SHOW_LEN] == '!'); } @@ -537,23 +537,23 @@ int main(int argc, char *argv[]) char buf[OPT_SHOW_LEN+2] = { 0 }; buf[OPT_SHOW_LEN] = '!'; i = -7777; - opt_show_longlongval_bi(buf, &i); + opt_show_longlongval_bi(buf, OPT_SHOW_LEN, &i); ok1(strcmp(buf, "-7777") == 0); ok1(buf[OPT_SHOW_LEN] == '!'); i = 7777; - opt_show_longlongval_bi(buf, &i); + opt_show_longlongval_bi(buf, OPT_SHOW_LEN, &i); ok1(strcmp(buf, "7777") == 0); ok1(buf[OPT_SHOW_LEN] == '!'); i = -10240000 * k; - opt_show_longlongval_bi(buf, &i); + opt_show_longlongval_bi(buf, OPT_SHOW_LEN, &i); ok1(strcmp(buf, "-10000M") == 0); ok1(buf[OPT_SHOW_LEN] == '!'); i = 5 * P; - opt_show_longlongval_bi(buf, &i); + opt_show_longlongval_bi(buf, OPT_SHOW_LEN, &i); ok1(strcmp(buf, "5P") == 0); ok1(buf[OPT_SHOW_LEN] == '!'); i = 1024 * P; - opt_show_longlongval_bi(buf, &i); + opt_show_longlongval_bi(buf, OPT_SHOW_LEN, &i); ok1(strcmp(buf, "1E") == 0); ok1(buf[OPT_SHOW_LEN] == '!'); } @@ -564,19 +564,19 @@ int main(int argc, char *argv[]) char buf[OPT_SHOW_LEN+2] = { 0 }; buf[OPT_SHOW_LEN] = '!'; i = 77; - opt_show_uintval_bi(buf, &i); + opt_show_uintval_bi(buf, OPT_SHOW_LEN, &i); ok1(strcmp(buf, "77") == 0); ok1(buf[OPT_SHOW_LEN] == '!'); i = 1234 * k; - opt_show_uintval_bi(buf, &i); + opt_show_uintval_bi(buf, OPT_SHOW_LEN, &i); ok1(strcmp(buf, "1234k") == 0); ok1(buf[OPT_SHOW_LEN] == '!'); i = 500 * M; - opt_show_uintval_bi(buf, &i); + opt_show_uintval_bi(buf, OPT_SHOW_LEN, &i); ok1(strcmp(buf, "500M") == 0); ok1(buf[OPT_SHOW_LEN] == '!'); i = 1024 * M; - opt_show_uintval_bi(buf, &i); + opt_show_uintval_bi(buf, OPT_SHOW_LEN, &i); ok1(strcmp(buf, "1G") == 0); ok1(buf[OPT_SHOW_LEN] == '!'); } @@ -587,23 +587,23 @@ int main(int argc, char *argv[]) char buf[OPT_SHOW_LEN+2] = { 0 }; buf[OPT_SHOW_LEN] = '!'; i = 77; - opt_show_ulongval_bi(buf, &i); + opt_show_ulongval_bi(buf, OPT_SHOW_LEN, &i); ok1(strcmp(buf, "77") == 0); ok1(buf[OPT_SHOW_LEN] == '!'); i = k; - opt_show_ulongval_bi(buf, &i); + opt_show_ulongval_bi(buf, OPT_SHOW_LEN, &i); ok1(strcmp(buf, "1k") == 0); ok1(buf[OPT_SHOW_LEN] == '!'); i = 500 * M; - opt_show_ulongval_bi(buf, &i); + opt_show_ulongval_bi(buf, OPT_SHOW_LEN, &i); ok1(strcmp(buf, "500M") == 0); ok1(buf[OPT_SHOW_LEN] == '!'); i = 1024 * M; - opt_show_ulongval_bi(buf, &i); + opt_show_ulongval_bi(buf, OPT_SHOW_LEN, &i); ok1(strcmp(buf, "1G") == 0); ok1(buf[OPT_SHOW_LEN] == '!'); i = 0; - opt_show_ulongval_bi(buf, &i); + opt_show_ulongval_bi(buf, OPT_SHOW_LEN, &i); ok1(strcmp(buf, "0") == 0); ok1(buf[OPT_SHOW_LEN] == '!'); } @@ -614,19 +614,19 @@ int main(int argc, char *argv[]) char buf[OPT_SHOW_LEN+2] = { 0 }; buf[OPT_SHOW_LEN] = '!'; i = 7777; - opt_show_ulonglongval_bi(buf, (unsigned long long *)&i); + opt_show_ulonglongval_bi(buf, OPT_SHOW_LEN, (unsigned long long *)&i); ok1(strcmp(buf, "7777") == 0); ok1(buf[OPT_SHOW_LEN] == '!'); i = 10240000 * k; - opt_show_ulonglongval_bi(buf, (unsigned long long *)&i); + opt_show_ulonglongval_bi(buf, OPT_SHOW_LEN, (unsigned long long *)&i); ok1(strcmp(buf, "10000M") == 0); ok1(buf[OPT_SHOW_LEN] == '!'); i = 5 * P; - opt_show_ulonglongval_bi(buf, (unsigned long long *)&i); + opt_show_ulonglongval_bi(buf, OPT_SHOW_LEN, (unsigned long long *)&i); ok1(strcmp(buf, "5P") == 0); ok1(buf[OPT_SHOW_LEN] == '!'); i = 1024 * P; - opt_show_ulonglongval_bi(buf, (unsigned long long *)&i); + opt_show_ulonglongval_bi(buf, OPT_SHOW_LEN, (unsigned long long *)&i); ok1(strcmp(buf, "1E") == 0); ok1(buf[OPT_SHOW_LEN] == '!'); } @@ -860,26 +860,26 @@ int main(int argc, char *argv[]) char buf[OPT_SHOW_LEN+2] = { 0 }; buf[OPT_SHOW_LEN] = '!'; i = -77; - opt_show_intval_si(buf, &i); + opt_show_intval_si(buf, OPT_SHOW_LEN, &i); ok1(strcmp(buf, "-77") == 0); i = 0; - opt_show_intval_si(buf, &i); + opt_show_intval_si(buf, OPT_SHOW_LEN, &i); ok1(strcmp(buf, "0") == 0); ok1(buf[OPT_SHOW_LEN] == '!'); i = 77; - opt_show_intval_si(buf, &i); + opt_show_intval_si(buf, OPT_SHOW_LEN, &i); ok1(strcmp(buf, "77") == 0); ok1(buf[OPT_SHOW_LEN] == '!'); i = -1234 * k; - opt_show_intval_si(buf, &i); + opt_show_intval_si(buf, OPT_SHOW_LEN, &i); ok1(strcmp(buf, "-1234k") == 0); ok1(buf[OPT_SHOW_LEN] == '!'); i = 500 * M; - opt_show_intval_si(buf, &i); + opt_show_intval_si(buf, OPT_SHOW_LEN, &i); ok1(strcmp(buf, "500M") == 0); ok1(buf[OPT_SHOW_LEN] == '!'); i = 1000 * M; - opt_show_intval_si(buf, &i); + opt_show_intval_si(buf, OPT_SHOW_LEN, &i); ok1(strcmp(buf, "1G") == 0); ok1(buf[OPT_SHOW_LEN] == '!'); } @@ -890,27 +890,27 @@ int main(int argc, char *argv[]) char buf[OPT_SHOW_LEN+2] = { 0 }; buf[OPT_SHOW_LEN] = '!'; i = -77; - opt_show_longval_si(buf, &i); + opt_show_longval_si(buf, OPT_SHOW_LEN, &i); ok1(strcmp(buf, "-77") == 0); ok1(buf[OPT_SHOW_LEN] == '!'); i = 77; - opt_show_longval_si(buf, &i); + opt_show_longval_si(buf, OPT_SHOW_LEN, &i); ok1(strcmp(buf, "77") == 0); ok1(buf[OPT_SHOW_LEN] == '!'); i = -1 * k; - opt_show_longval_si(buf, &i); + opt_show_longval_si(buf, OPT_SHOW_LEN, &i); ok1(strcmp(buf, "-1k") == 0); ok1(buf[OPT_SHOW_LEN] == '!'); i = 500 * M; - opt_show_longval_si(buf, &i); + opt_show_longval_si(buf, OPT_SHOW_LEN, &i); ok1(strcmp(buf, "500M") == 0); ok1(buf[OPT_SHOW_LEN] == '!'); i = 1000 * M; - opt_show_longval_si(buf, &i); + opt_show_longval_si(buf, OPT_SHOW_LEN, &i); ok1(strcmp(buf, "1G") == 0); ok1(buf[OPT_SHOW_LEN] == '!'); i = 0; - opt_show_longval_si(buf, &i); + opt_show_longval_si(buf, OPT_SHOW_LEN, &i); ok1(strcmp(buf, "0") == 0); ok1(buf[OPT_SHOW_LEN] == '!'); } @@ -921,23 +921,23 @@ int main(int argc, char *argv[]) char buf[OPT_SHOW_LEN+2] = { 0 }; buf[OPT_SHOW_LEN] = '!'; i = -7777; - opt_show_longlongval_si(buf, &i); + opt_show_longlongval_si(buf, OPT_SHOW_LEN, &i); ok1(strcmp(buf, "-7777") == 0); ok1(buf[OPT_SHOW_LEN] == '!'); i = 7777; - opt_show_longlongval_si(buf, &i); + opt_show_longlongval_si(buf, OPT_SHOW_LEN, &i); ok1(strcmp(buf, "7777") == 0); ok1(buf[OPT_SHOW_LEN] == '!'); i = -10240000 * k; - opt_show_longlongval_si(buf, &i); + opt_show_longlongval_si(buf, OPT_SHOW_LEN, &i); ok1(strcmp(buf, "-10240M") == 0); ok1(buf[OPT_SHOW_LEN] == '!'); i = 5 * P; - opt_show_longlongval_si(buf, &i); + opt_show_longlongval_si(buf, OPT_SHOW_LEN, &i); ok1(strcmp(buf, "5P") == 0); ok1(buf[OPT_SHOW_LEN] == '!'); i = 2000 * P; - opt_show_longlongval_si(buf, &i); + opt_show_longlongval_si(buf, OPT_SHOW_LEN, &i); ok1(strcmp(buf, "2E") == 0); ok1(buf[OPT_SHOW_LEN] == '!'); } @@ -948,19 +948,19 @@ int main(int argc, char *argv[]) char buf[OPT_SHOW_LEN+2] = { 0 }; buf[OPT_SHOW_LEN] = '!'; i = 77; - opt_show_uintval_si(buf, &i); + opt_show_uintval_si(buf, OPT_SHOW_LEN, &i); ok1(strcmp(buf, "77") == 0); ok1(buf[OPT_SHOW_LEN] == '!'); i = 1234 * k; - opt_show_uintval_si(buf, &i); + opt_show_uintval_si(buf, OPT_SHOW_LEN, &i); ok1(strcmp(buf, "1234k") == 0); ok1(buf[OPT_SHOW_LEN] == '!'); i = 500 * M; - opt_show_uintval_si(buf, &i); + opt_show_uintval_si(buf, OPT_SHOW_LEN, &i); ok1(strcmp(buf, "500M") == 0); ok1(buf[OPT_SHOW_LEN] == '!'); i = 1000 * M; - opt_show_uintval_si(buf, &i); + opt_show_uintval_si(buf, OPT_SHOW_LEN, &i); ok1(strcmp(buf, "1G") == 0); ok1(buf[OPT_SHOW_LEN] == '!'); } @@ -971,23 +971,23 @@ int main(int argc, char *argv[]) char buf[OPT_SHOW_LEN+2] = { 0 }; buf[OPT_SHOW_LEN] = '!'; i = 77; - opt_show_ulongval_si(buf, &i); + opt_show_ulongval_si(buf, OPT_SHOW_LEN, &i); ok1(strcmp(buf, "77") == 0); ok1(buf[OPT_SHOW_LEN] == '!'); i = k; - opt_show_ulongval_si(buf, &i); + opt_show_ulongval_si(buf, OPT_SHOW_LEN, &i); ok1(strcmp(buf, "1k") == 0); ok1(buf[OPT_SHOW_LEN] == '!'); i = 500 * M; - opt_show_ulongval_si(buf, &i); + opt_show_ulongval_si(buf, OPT_SHOW_LEN, &i); ok1(strcmp(buf, "500M") == 0); ok1(buf[OPT_SHOW_LEN] == '!'); i = 1024 * M; - opt_show_ulongval_si(buf, &i); + opt_show_ulongval_si(buf, OPT_SHOW_LEN, &i); ok1(strcmp(buf, "1024M") == 0); ok1(buf[OPT_SHOW_LEN] == '!'); i = 0; - opt_show_ulongval_si(buf, &i); + opt_show_ulongval_si(buf, OPT_SHOW_LEN, &i); ok1(strcmp(buf, "0") == 0); ok1(buf[OPT_SHOW_LEN] == '!'); } @@ -998,19 +998,19 @@ int main(int argc, char *argv[]) char buf[OPT_SHOW_LEN+2] = { 0 }; buf[OPT_SHOW_LEN] = '!'; i = 7777; - opt_show_ulonglongval_si(buf, (unsigned long long *)&i); + opt_show_ulonglongval_si(buf, OPT_SHOW_LEN, (unsigned long long *)&i); ok1(strcmp(buf, "7777") == 0); ok1(buf[OPT_SHOW_LEN] == '!'); i = 10240000 * k; - opt_show_ulonglongval_si(buf, (unsigned long long *)&i); + opt_show_ulonglongval_si(buf, OPT_SHOW_LEN, (unsigned long long *)&i); ok1(strcmp(buf, "10240M") == 0); ok1(buf[OPT_SHOW_LEN] == '!'); i = 5 * P; - opt_show_ulonglongval_si(buf, (unsigned long long *)&i); + opt_show_ulonglongval_si(buf, OPT_SHOW_LEN, (unsigned long long *)&i); ok1(strcmp(buf, "5P") == 0); ok1(buf[OPT_SHOW_LEN] == '!'); i = 1000 * P; - opt_show_ulonglongval_si(buf, (unsigned long long *)&i); + opt_show_ulonglongval_si(buf, OPT_SHOW_LEN, (unsigned long long *)&i); ok1(strcmp(buf, "1E") == 0); ok1(buf[OPT_SHOW_LEN] == '!'); } @@ -1090,12 +1090,12 @@ int main(int argc, char *argv[]) buf[OPT_SHOW_LEN] = '!'; b = true; - opt_show_bool(buf, &b); + opt_show_bool(buf, OPT_SHOW_LEN, &b); ok1(strcmp(buf, "true") == 0); ok1(buf[OPT_SHOW_LEN] == '!'); b = false; - opt_show_bool(buf, &b); + opt_show_bool(buf, OPT_SHOW_LEN, &b); ok1(strcmp(buf, "false") == 0); ok1(buf[OPT_SHOW_LEN] == '!'); } @@ -1107,12 +1107,12 @@ int main(int argc, char *argv[]) buf[OPT_SHOW_LEN] = '!'; b = true; - opt_show_invbool(buf, &b); + opt_show_invbool(buf, OPT_SHOW_LEN, &b); ok1(strcmp(buf, "false") == 0); ok1(buf[OPT_SHOW_LEN] == '!'); b = false; - opt_show_invbool(buf, &b); + opt_show_invbool(buf, OPT_SHOW_LEN, &b); ok1(strcmp(buf, "true") == 0); ok1(buf[OPT_SHOW_LEN] == '!'); } @@ -1126,14 +1126,14 @@ int main(int argc, char *argv[]) /* Short test. */ p = str; strcpy(p, "short"); - opt_show_charp(buf, &p); + opt_show_charp(buf, OPT_SHOW_LEN, &p); ok1(strcmp(buf, "\"short\"") == 0); ok1(buf[OPT_SHOW_LEN] == '!'); /* Truncate test. */ memset(p, 'x', OPT_SHOW_LEN*2); p[OPT_SHOW_LEN*2-1] = '\0'; - opt_show_charp(buf, &p); + opt_show_charp(buf, OPT_SHOW_LEN, &p); ok1(buf[0] == '"'); ok1(buf[OPT_SHOW_LEN-1] == '"'); ok1(buf[OPT_SHOW_LEN] == '!'); @@ -1147,12 +1147,12 @@ int main(int argc, char *argv[]) buf[OPT_SHOW_LEN] = '!'; i = -77; - opt_show_intval(buf, &i); + opt_show_intval(buf, OPT_SHOW_LEN, &i); ok1(strcmp(buf, "-77") == 0); ok1(buf[OPT_SHOW_LEN] == '!'); i = 77; - opt_show_intval(buf, &i); + opt_show_intval(buf, OPT_SHOW_LEN, &i); ok1(strcmp(buf, "77") == 0); ok1(buf[OPT_SHOW_LEN] == '!'); } @@ -1164,7 +1164,7 @@ int main(int argc, char *argv[]) buf[OPT_SHOW_LEN] = '!'; ui = 4294967295U; - opt_show_uintval(buf, &ui); + opt_show_uintval(buf, OPT_SHOW_LEN, &ui); ok1(strcmp(buf, "4294967295") == 0); ok1(buf[OPT_SHOW_LEN] == '!'); } @@ -1176,7 +1176,7 @@ int main(int argc, char *argv[]) buf[OPT_SHOW_LEN] = '!'; l = 1234567890L; - opt_show_longval(buf, &l); + opt_show_longval(buf, OPT_SHOW_LEN, &l); ok1(strcmp(buf, "1234567890") == 0); ok1(buf[OPT_SHOW_LEN] == '!'); } @@ -1188,7 +1188,7 @@ int main(int argc, char *argv[]) buf[OPT_SHOW_LEN] = '!'; ul = 4294967295UL; - opt_show_ulongval(buf, &ul); + opt_show_ulongval(buf, OPT_SHOW_LEN, &ul); ok1(strcmp(buf, "4294967295") == 0); ok1(buf[OPT_SHOW_LEN] == '!'); } @@ -1200,12 +1200,12 @@ int main(int argc, char *argv[]) buf[OPT_SHOW_LEN] = '!'; f = -77.5; - opt_show_floatval(buf, &f); + opt_show_floatval(buf, OPT_SHOW_LEN, &f); ok1(strcmp(buf, "-77.500000") == 0); ok1(buf[OPT_SHOW_LEN] == '!'); f = 77.5; - opt_show_floatval(buf, &f); + opt_show_floatval(buf, OPT_SHOW_LEN, &f); ok1(strcmp(buf, "77.500000") == 0); ok1(buf[OPT_SHOW_LEN] == '!'); } @@ -1217,12 +1217,12 @@ int main(int argc, char *argv[]) buf[OPT_SHOW_LEN] = '!'; d = -77; - opt_show_doubleval(buf, &d); + opt_show_doubleval(buf, OPT_SHOW_LEN, &d); ok1(strcmp(buf, "-77.000000") == 0); ok1(buf[OPT_SHOW_LEN] == '!'); d = 77; - opt_show_doubleval(buf, &d); + opt_show_doubleval(buf, OPT_SHOW_LEN, &d); ok1(strcmp(buf, "77.000000") == 0); ok1(buf[OPT_SHOW_LEN] == '!'); } diff --git a/ccan/opt/test/run-set_alloc.c b/ccan/opt/test/run-set_alloc.c index 1dbb351b..2d7410ae 100644 --- a/ccan/opt/test/run-set_alloc.c +++ b/ccan/opt/test/run-set_alloc.c @@ -59,8 +59,8 @@ static void *reallocfn(void *ptr, size_t size) static void freefn(void *ptr) { free_count++; - free(ptr); *find_ptr(ptr) = NULL; + free(ptr); } int main(int argc, char *argv[]) diff --git a/ccan/opt/test/run-userbits.c b/ccan/opt/test/run-userbits.c new file mode 100644 index 00000000..7f102f08 --- /dev/null +++ b/ccan/opt/test/run-userbits.c @@ -0,0 +1,59 @@ +#include +#include +#include +#include +#include +#include +#include "utils.h" + +int main(int argc, char *argv[]) +{ + const char *myname = argv[0]; + + plan_tests(28); + + opt_register_noarg("-a", test_noarg, NULL, "All"); + opt_register_noarg("--aaa", test_noarg, NULL, "AAAAll"); + opt_register_arg("-b|--bbb", test_arg, NULL, "bbb", "AAAAAAll"); + + ok1(strcmp(opt_table[0].names, "-a") == 0); + ok1(opt_table[0].type == OPT_NOARG); + ok1(strcmp(opt_table[1].names, "--aaa") == 0); + ok1(opt_table[1].type == OPT_NOARG); + ok1(strcmp(opt_table[2].names, "-b|--bbb") == 0); + ok1(opt_table[2].type == OPT_HASARG); + + opt_table[0].type |= (1 << OPT_USER_START); + opt_table[1].type |= ((1 << OPT_USER_END)-1) - ((1 << OPT_USER_START)-1); + opt_table[2].type |= (1 << OPT_USER_END); + + /* Should all work fine! */ + ok1(parse_args(&argc, &argv, "-a", NULL)); + ok1(argc == 1); + ok1(argv[0] == myname); + ok1(test_cb_called == 1); + + ok1(parse_args(&argc, &argv, "--aaa", NULL)); + ok1(argc == 1); + ok1(argv[0] == myname); + ok1(test_cb_called == 2); + + /* This one needs an arg. */ + ok1(parse_args(&argc, &argv, "-b", NULL) == false); + ok1(test_cb_called == 2); + ok1(parse_args(&argc, &argv, "-b", "bbb", NULL)); + ok1(argc == 1); + ok1(argv[0] == myname); + ok1(argv[1] == NULL); + ok1(test_cb_called == 3); + + ok1(parse_args(&argc, &argv, "--bbb", "bbb", NULL)); + ok1(argc == 1); + ok1(argv[0] == myname); + ok1(argv[1] == NULL); + ok1(test_cb_called == 4); + + /* parse_args allocates argv */ + free(argv); + return exit_status(); +} diff --git a/ccan/opt/test/utils.c b/ccan/opt/test/utils.c index 2ff04884..61199fb4 100644 --- a/ccan/opt/test/utils.c +++ b/ccan/opt/test/utils.c @@ -21,9 +21,10 @@ char *test_arg(const char *optarg, const char *arg) return NULL; } -void show_arg(char buf[OPT_SHOW_LEN], const char *arg) +bool show_arg(char *buf, size_t len, const char *arg) { - strncpy(buf, arg, OPT_SHOW_LEN); + strncpy(buf, arg, len); + return true; } char *err_output = NULL; diff --git a/ccan/opt/test/utils.h b/ccan/opt/test/utils.h index 12cf0b75..3ada62d1 100644 --- a/ccan/opt/test/utils.h +++ b/ccan/opt/test/utils.h @@ -13,7 +13,7 @@ void reset_options(void); extern unsigned int test_cb_called; char *test_noarg(void *arg); char *test_arg(const char *optarg, const char *arg); -void show_arg(char buf[OPT_SHOW_LEN], const char *arg); +bool show_arg(char *buf, size_t len, const char *arg); extern struct opt_table short_table[]; extern struct opt_table long_table[]; diff --git a/ccan/opt/usage.c b/ccan/opt/usage.c index 12f44a48..568e4661 100644 --- a/ccan/opt/usage.c +++ b/ccan/opt/usage.c @@ -20,6 +20,9 @@ const char opt_hidden[1]; #define MIN_DESC_WIDTH 40 #define MIN_TOTAL_WIDTH 50 +/* Maximum length of arg to show in opt_usage */ +#define OPT_SHOW_LEN 80 + static unsigned int get_columns(void) { int ws_col = 0; @@ -72,7 +75,8 @@ static size_t consume_words(const char *words, size_t maxlen, size_t *prefix, } } - *start = (words[oldlen - 1] == '\n'); + if (oldlen != 0) + *start = (words[oldlen - 1] == '\n'); return oldlen; } @@ -147,20 +151,20 @@ static char *add_desc(char *base, size_t *len, size_t *max, if (opt->show) { char buf[OPT_SHOW_LEN + sizeof("...")]; strcpy(buf + OPT_SHOW_LEN, "..."); - opt->show(buf, opt->u.arg); + if (opt->show(buf, OPT_SHOW_LEN, opt->u.arg)) { + /* If it doesn't fit on this line, indent. */ + if (off + strlen(" (default: ") + strlen(buf) + strlen(")") + > width) { + base = add_indent(base, len, max, indent); + } else { + /* Remove \n. */ + (*len)--; + } - /* If it doesn't fit on this line, indent. */ - if (off + strlen(" (default: ") + strlen(buf) + strlen(")") - > width) { - base = add_indent(base, len, max, indent); - } else { - /* Remove \n. */ - (*len)--; + base = add_str(base, len, max, " (default: "); + base = add_str(base, len, max, buf); + base = add_str(base, len, max, ")\n"); } - - base = add_str(base, len, max, " (default: "); - base = add_str(base, len, max, buf); - base = add_str(base, len, max, ")\n"); } return base; } @@ -181,10 +185,10 @@ char *opt_usage(const char *argv0, const char *extra) size_t l; if (opt_table[i].desc == opt_hidden) continue; - if (opt_table[i].type == OPT_SUBTABLE) + if (opt_table[i].type & OPT_SUBTABLE) continue; l = strlen(opt_table[i].names); - if (opt_table[i].type == OPT_HASARG + if ((opt_table[i].type & OPT_HASARG) && !strchr(opt_table[i].names, ' ') && !strchr(opt_table[i].names, '=')) l += strlen(" "); @@ -220,7 +224,7 @@ char *opt_usage(const char *argv0, const char *extra) for (i = 0; i < opt_count; i++) { if (opt_table[i].desc == opt_hidden) continue; - if (opt_table[i].type == OPT_SUBTABLE) { + if (opt_table[i].type & OPT_SUBTABLE) { ret = add_str(ret, &len, &max, opt_table[i].desc); ret = add_str(ret, &len, &max, ":\n"); continue; diff --git a/ccan/pipecmd/_info b/ccan/pipecmd/_info index 8c49511a..824f22e3 100644 --- a/ccan/pipecmd/_info +++ b/ccan/pipecmd/_info @@ -51,6 +51,7 @@ int main(int argc, char *argv[]) return 1; if (strcmp(argv[1], "depends") == 0) { + printf("ccan/closefrom\n"); printf("ccan/noerr\n"); return 0; } diff --git a/ccan/pipecmd/pipecmd.c b/ccan/pipecmd/pipecmd.c index afeaf5a0..0090275b 100644 --- a/ccan/pipecmd/pipecmd.c +++ b/ccan/pipecmd/pipecmd.c @@ -1,4 +1,5 @@ /* CC0 license (public domain) - see LICENSE file for details */ +#include #include #include #include @@ -115,7 +116,8 @@ pid_t pipecmdarr(int *fd_tochild, int *fd_fromchild, int *fd_errfromchild, goto fail; if (childpid == 0) { - for (int i = 0; i < num_child_close; i++) + int i; + for (i = 0; i < num_child_close; i++) close(child_close[i]); // Child runs command. @@ -138,11 +140,21 @@ pid_t pipecmdarr(int *fd_tochild, int *fd_fromchild, int *fd_errfromchild, close(errfromchild[1]); } + /* Map execfail[1] to fd 3. */ + if (execfail[1] != 3) { + if (dup2(execfail[1], 3) == -1) + goto child_errno_fail; + /* CLOEXEC is not shared by dup2, so copy the flags + * from execfail[1] to 3. + */ + if (fcntl(3, F_SETFD, fcntl(execfail[1], F_GETFD)) < 0) + goto child_errno_fail; + close(execfail[1]); + execfail[1] = 3; + } + /* Make (fairly!) sure all other fds are closed. */ - int max = sysconf(_SC_OPEN_MAX); - for (int i = 3; i < max; i++) - if (i != execfail[1]) - close(i); + closefrom(4); execvp(arr[0], arr); @@ -155,7 +167,8 @@ pid_t pipecmdarr(int *fd_tochild, int *fd_fromchild, int *fd_errfromchild, exit(127); } - for (int i = 0; i < num_par_close; i++) + int i; + for (i = 0; i < num_par_close; i++) close(par_close[i]); /* Child will close this without writing on successful exec. */ @@ -175,7 +188,7 @@ pid_t pipecmdarr(int *fd_tochild, int *fd_fromchild, int *fd_errfromchild, return childpid; fail: - for (int i = 0; i < num_par_close; i++) + for (i = 0; i < num_par_close; i++) close_noerr(par_close[i]); return -1; } diff --git a/ccan/rbuf/rbuf.c b/ccan/rbuf/rbuf.c index d8d658d3..cc10cf3d 100644 --- a/ccan/rbuf/rbuf.c +++ b/ccan/rbuf/rbuf.c @@ -74,9 +74,11 @@ char *rbuf_read_str(struct rbuf *rbuf, char term) ssize_t r = 0; size_t prev = 0; - while (!(p = memchr(membuf_elems(&rbuf->m) + prev, - term, - membuf_num_elems(&rbuf->m) - prev))) { + /* memchr(NULL, ..., 0) is illegal. FML. */ + while (membuf_num_elems(&rbuf->m) == prev + || !(p = memchr(membuf_elems(&rbuf->m) + prev, + term, + membuf_num_elems(&rbuf->m) - prev))) { prev += r; r = get_more(rbuf); if (r < 0) diff --git a/ccan/rune/LICENSE b/ccan/rune/LICENSE new file mode 120000 index 00000000..2354d129 --- /dev/null +++ b/ccan/rune/LICENSE @@ -0,0 +1 @@ +../../licenses/BSD-MIT \ No newline at end of file diff --git a/ccan/rune/_info b/ccan/rune/_info new file mode 100644 index 00000000..2b2e2e8b --- /dev/null +++ b/ccan/rune/_info @@ -0,0 +1,130 @@ +#include "config.h" +#include +#include + +/** + * rune - Simple cookies you can extend (a-la Python runes class). + * + * This code is a form of cookies, but they are user-extensible, and + * contain a simple language to define what the cookie allows. + * + * A "rune" contains the hash of a secret (so the server can + * validate), such that you can add, but not subtract, conditions. + * This is a simplified form of Macaroons, See + * https://research.google/pubs/pub41892/ "Macaroons: Cookies with + * Contextual Caveats for Decentralized Authorization in the Cloud". + * It has one good idea, some extended ideas nobody implements, and + * lots and lots of words. + * + * License: BSD-MIT + * Author: Rusty Russell + * Example: + * // Given "generate secret 1" outputs kr7AW-eJ2Munhv5ftu4rHqAnhxUpPQM8aOyWOmqiytk9MQ== + * // Given "add kr7AW-eJ2Munhv5ftu4rHqAnhxUpPQM8aOyWOmqiytk9MQ== uid=rusty" outputs Xyt5S6FKUnA2ppGB62c6HTPGojt2S7k2n7Cf7Tjj6zM9MSZ1aWQ9cnVzdHk= + * // Given "test secret Xyt5S6FKUnA2ppGB62c6HTPGojt2S7k2n7Cf7Tjj6zM9MSZ1aWQ9cnVzdHk= rusty" outputs PASSED + * // Given "test secret Xyt5S6FKUnA2ppGB62c6HTPGojt2S7k2n7Cf7Tjj6zM9MSZ1aWQ9cnVzdHk= notrusty" outputs FAILED: uid is not equal to rusty + * // Given "add Xyt5S6FKUnA2ppGB62c6HTPGojt2S7k2n7Cf7Tjj6zM9MSZ1aWQ9cnVzdHk= t\<1655958616" outputs _YBFmeAedqlLigWHAmvyyGGHRrnI40BRQGh2hWdSZ9E9MSZ1aWQ9cnVzdHkmdDwxNjU1OTU4NjE2 + * // Given "test secret _YBFmeAedqlLigWHAmvyyGGHRrnI40BRQGh2hWdSZ9E9MSZ1aWQ9cnVzdHkmdDwxNjU1OTU4NjE2 rusty" outputs FAILED: t is greater or equal to 1655958616 + * #include + * #include + * #include + * #include + * #include + * + * // We support two values: current time (t), and user id (uid). + * static const char *check(const tal_t *ctx, + * const struct rune *rune, + * const struct rune_altern *alt, + * char *uid) + * { + * // t= means current time, in seconds, as integer + * if (streq(alt->fieldname, "t")) { + * struct timeval now; + * gettimeofday(&now, NULL); + * return rune_alt_single_int(ctx, alt, now.tv_sec); + * } + * if (streq(alt->fieldname, "uid")) { + * return rune_alt_single_str(ctx, alt, uid, strlen(uid)); + * } + * // Otherwise, field is missing + * return rune_alt_single_missing(ctx, alt); + * } + * + * int main(int argc, char *argv[]) + * { + * struct rune *master, *rune; + * + * if (argc < 3) + * goto usage; + * + * if (streq(argv[1], "generate")) { + * // Make master, derive a unique_id'd rune. + * if (argc != 3 && argc != 4) + * goto usage; + * master = rune_new(NULL, (u8 *)argv[2], strlen(argv[2]), NULL); + * rune = rune_derive_start(NULL, master, argv[3]); + * } else if (streq(argv[1], "add")) { + * // Add a restriction + * struct rune_restr *restr; + * if (argc != 4) + * goto usage; + * rune = rune_from_base64(NULL, argv[2]); + * if (!rune) + * errx(1, "Bad rune"); + * restr = rune_restr_from_string(NULL, argv[3], strlen(argv[3])); + * if (!restr) + * errx(1, "Bad restriction string"); + * rune_add_restr(rune, restr); + * } else if (streq(argv[1], "test")) { + * const char *err; + * if (argc != 5) + * goto usage; + * master = rune_new(NULL, (u8 *)argv[2], strlen(argv[2]), NULL); + * if (!master) + * errx(1, "Bad master rune"); + * rune = rune_from_base64(NULL, argv[3]); + * if (!rune) + * errx(1, "Bad rune"); + * err = rune_test(NULL, master, rune, check, argv[4]); + * if (err) + * printf("FAILED: %s\n", err); + * else + * printf("PASSED\n"); + * return 0; + * } else + * goto usage; + * + * printf("%s\n", rune_to_base64(NULL, rune)); + * return 0; + * + * usage: + * errx(1, "Usage: %s generate OR\n" + * "%s add OR\n" + * "%s test ", argv[0], argv[0], argv[0]); + * } + */ +int main(int argc, char *argv[]) +{ + /* Expect exactly one argument */ + if (argc != 2) + return 1; + + if (strcmp(argv[1], "depends") == 0) { + printf("ccan/base64\n"); + printf("ccan/crypto/sha256\n"); + printf("ccan/endian\n"); + printf("ccan/mem\n"); + printf("ccan/short_types\n"); + printf("ccan/str/hex\n"); + printf("ccan/tal/str\n"); + printf("ccan/tal\n"); + printf("ccan/typesafe_cb\n"); + return 0; + } + if (strcmp(argv[1], "testdepends") == 0) { + printf("ccan/tal/grab_file\n"); + return 0; + } + + return 1; +} diff --git a/ccan/rune/coding.c b/ccan/rune/coding.c new file mode 100644 index 00000000..495d37c3 --- /dev/null +++ b/ccan/rune/coding.c @@ -0,0 +1,426 @@ +/* MIT (BSD) license - see LICENSE file for details */ +/* Routines to encode / decode a rune */ +#include +#include +#include +#include +#include +#include +#include + +/* From Python base64.urlsafe_b64encode: + * + * The alphabet uses '-' instead of '+' and '_' instead of '/'. + */ +static const base64_maps_t base64_maps_urlsafe = { + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_", + + "\xff\xff\xff\xff\xff" /* 0 */ + "\xff\xff\xff\xff\xff" /* 5 */ + "\xff\xff\xff\xff\xff" /* 10 */ + "\xff\xff\xff\xff\xff" /* 15 */ + "\xff\xff\xff\xff\xff" /* 20 */ + "\xff\xff\xff\xff\xff" /* 25 */ + "\xff\xff\xff\xff\xff" /* 30 */ + "\xff\xff\xff\xff\xff" /* 35 */ + "\xff\xff\xff\xff\xff" /* 40 */ + "\x3e\xff\xff\x34\x35" /* 45 */ + "\x36\x37\x38\x39\x3a" /* 50 */ + "\x3b\x3c\x3d\xff\xff" /* 55 */ + "\xff\xff\xff\xff\xff" /* 60 */ + "\x00\x01\x02\x03\x04" /* 65 A */ + "\x05\x06\x07\x08\x09" /* 70 */ + "\x0a\x0b\x0c\x0d\x0e" /* 75 */ + "\x0f\x10\x11\x12\x13" /* 80 */ + "\x14\x15\x16\x17\x18" /* 85 */ + "\x19\xff\xff\xff\xff" /* 90 */ + "\x3f\xff\x1a\x1b\x1c" /* 95 */ + "\x1d\x1e\x1f\x20\x21" /* 100 */ + "\x22\x23\x24\x25\x26" /* 105 */ + "\x27\x28\x29\x2a\x2b" /* 110 */ + "\x2c\x2d\x2e\x2f\x30" /* 115 */ + "\x31\x32\x33\xff\xff" /* 120 */ + "\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff" /* 125 */ + "\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff" /* 135 */ + "\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff" /* 145 */ + "\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff" /* 155 */ + "\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff" /* 165 */ + "\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff" /* 175 */ + "\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff" /* 185 */ + "\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff" /* 195 */ + "\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff" /* 205 */ + "\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff" /* 215 */ + "\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff" /* 225 */ + "\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff" /* 235 */ + "\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff" /* 245 */ +}; + +/* For encoding as a string */ +struct wbuf { + size_t off, len; + char *buf; +}; + +static void to_wbuf(const char *s, size_t len, void *vwbuf) +{ + struct wbuf *wbuf = vwbuf; + + while (wbuf->off + len > wbuf->len) + tal_resize(&wbuf->buf, wbuf->len *= 2); + memcpy(wbuf->buf + wbuf->off, s, len); + wbuf->off += len; +} + +/* For adding to sha256 */ +static void to_sha256(const char *s, size_t len, void *vshactx) +{ + struct sha256_ctx *shactx = vshactx; + sha256_update(shactx, s, len); +} + +static void rune_altern_encode(const struct rune_altern *altern, + void (*cb)(const char *s, size_t len, + void *arg), + void *arg) +{ + char cond = altern->condition; + const char *p; + + cb(altern->fieldname, strlen(altern->fieldname), arg); + cb(&cond, 1, arg); + + p = altern->value; + for (;;) { + char esc[2] = { '\\' }; + size_t len = strcspn(p, "\\|&"); + cb(p, len, arg); + if (!p[len]) + break; + esc[1] = p[len]; + cb(esc, 2, arg); + p += len + 1; + } +} + +static void rune_restr_encode(const struct rune_restr *restr, + void (*cb)(const char *s, size_t len, + void *arg), + void *arg) +{ + for (size_t i = 0; i < tal_count(restr->alterns); i++) { + if (i != 0) + cb("|", 1, arg); + rune_altern_encode(restr->alterns[i], cb, arg); + } +} + +void rune_sha256_add_restr(struct sha256_ctx *shactx, + struct rune_restr *restr) +{ + rune_restr_encode(restr, to_sha256, shactx); + rune_sha256_endmarker(shactx); +} + +const char *rune_is_derived(const struct rune *source, const struct rune *rune) +{ + if (!runestr_eq(source->version, rune->version)) + return "Version mismatch"; + + return rune_is_derived_anyversion(source, rune); +} + +const char *rune_is_derived_anyversion(const struct rune *source, + const struct rune *rune) +{ + struct sha256_ctx shactx; + size_t i; + + if (tal_count(rune->restrs) < tal_count(source->restrs)) + return "Fewer restrictions than master"; + + /* If we add the same restrictions to source rune, do we match? */ + shactx = source->shactx; + for (i = 0; i < tal_count(rune->restrs); i++) { + /* First restrictions must be identical */ + if (i < tal_count(source->restrs)) { + if (!rune_restr_eq(source->restrs[i], rune->restrs[i])) + return "Does not match master restrictions"; + } else + rune_sha256_add_restr(&shactx, rune->restrs[i]); + } + + if (memcmp(shactx.s, rune->shactx.s, sizeof(shactx.s)) != 0) + return "Not derived from master"; + return NULL; +} + +static bool peek_char(const char *data, size_t len, char *c) +{ + if (len == 0) + return false; + *c = *data; + return true; +} + +static void drop_char(const char **data, size_t *len) +{ + (*data)++; + (*len)--; +} + +static void pull_invalid(const char **data, size_t *len) +{ + *data = NULL; + *len = 0; +} + +static bool pull_char(const char **data, size_t *len, char *c) +{ + if (!peek_char(*data, *len, c)) { + pull_invalid(data, len); + return false; + } + drop_char(data, len); + return true; +} + +bool rune_condition_is_valid(enum rune_condition cond) +{ + switch (cond) { + case RUNE_COND_IF_MISSING: + case RUNE_COND_EQUAL: + case RUNE_COND_NOT_EQUAL: + case RUNE_COND_BEGINS: + case RUNE_COND_ENDS: + case RUNE_COND_CONTAINS: + case RUNE_COND_INT_LESS: + case RUNE_COND_INT_GREATER: + case RUNE_COND_LEXO_BEFORE: + case RUNE_COND_LEXO_AFTER: + case RUNE_COND_COMMENT: + return true; + } + return false; +} + +size_t rune_altern_fieldname_len(const char *alternstr, size_t alternstrlen) +{ + for (size_t i = 0; i < alternstrlen; i++) { + if (cispunct(alternstr[i]) && alternstr[i] != '_') + return i; + } + return alternstrlen; +} + +/* Sets *more on success: true if another altern follows */ +static struct rune_altern *rune_altern_decode(const tal_t *ctx, + const char **data, size_t *len, + bool *more) +{ + struct rune_altern *alt = tal(ctx, struct rune_altern); + char *value; + size_t strlen; + char c; + + /* Swallow field up to possible conditional */ + strlen = rune_altern_fieldname_len(*data, *len); + alt->fieldname = tal_strndup(alt, *data, strlen); + *data += strlen; + *len -= strlen; + + /* Grab conditional */ + if (!pull_char(data, len, &c) || !rune_condition_is_valid(c)) + return tal_free(alt); + + alt->condition = c; + + /* Assign worst case. */ + value = tal_arr(alt, char, *len + 1); + strlen = 0; + *more = false; + while (*len && pull_char(data, len, &c)) { + if (c == '|') { + *more = true; + break; + } + if (c == '&') + break; + + if (c == '\\' && !pull_char(data, len, &c)) + return tal_free(alt); + value[strlen++] = c; + } + value[strlen] = '\0'; + tal_resize(&value, strlen + 1); + alt->value = value; + return alt; +} + +static struct rune_restr *rune_restr_decode(const tal_t *ctx, + const char **data, size_t *len) +{ + struct rune_restr *restr = tal(ctx, struct rune_restr); + size_t num_alts = 0; + bool more; + + /* Must have at least one! */ + restr->alterns = tal_arr(restr, struct rune_altern *, 0); + do { + struct rune_altern *alt; + + alt = rune_altern_decode(restr, data, len, &more); + if (!alt) + return tal_free(restr); + tal_resize(&restr->alterns, num_alts+1); + restr->alterns[num_alts++] = alt; + } while (more); + return restr; +} + +static struct rune *from_string(const tal_t *ctx, + const char *str, + const u8 *hash32) +{ + size_t len = strlen(str); + struct rune *rune = tal(ctx, struct rune); + + /* Now count up how many bytes we should have hashed: secret uses + * first block. */ + rune->shactx.bytes = 64; + + rune->restrs = tal_arr(rune, struct rune_restr *, 0); + rune->unique_id = NULL; + rune->version = NULL; + + while (len) { + struct rune_restr *restr; + restr = rune_restr_decode(rune, &str, &len); + if (!restr) + return tal_free(rune); + if (!rune_add_restr(rune, restr)) + return tal_free(rune); + } + + /* Now we replace with canned hash state */ + memcpy(rune->shactx.s, hash32, 32); + for (size_t i = 0; i < 8; i++) + rune->shactx.s[i] = be32_to_cpu(rune->shactx.s[i]); + + return rune; +} + +struct rune_restr *rune_restr_from_string(const tal_t *ctx, + const char *str, + size_t len) +{ + struct rune_restr *restr; + + restr = rune_restr_decode(NULL, &str, &len); + /* Don't allow trailing chars */ + if (restr && len != 0) + restr = tal_free(restr); + return tal_steal(ctx, restr); +} + +static void to_string(struct wbuf *wbuf, const struct rune *rune, u8 *hash32) +{ + /* Copy hash in big-endian */ + for (size_t i = 0; i < 8; i++) { + be32 v = cpu_to_be32(rune->shactx.s[i]); + memcpy(hash32 + i*4, &v, sizeof(v)); + } + + for (size_t i = 0; i < tal_count(rune->restrs); i++) { + if (i != 0) + to_wbuf("&", 1, wbuf); + rune_restr_encode(rune->restrs[i], to_wbuf, wbuf); + } + to_wbuf("", 1, wbuf); +} + +struct rune *rune_from_base64n(const tal_t *ctx, const char *str, size_t len) +{ + size_t blen; + u8 *data; + struct rune *rune; + + data = tal_arr(NULL, u8, base64_decoded_length(len) + 1); + + blen = base64_decode_using_maps(&base64_maps_urlsafe, + (char *)data, tal_bytelen(data), + str, len); + if (blen == -1) + goto fail; + + if (blen < 32) + goto fail; + + data[blen] = '\0'; + /* Sanity check that it's a valid string! */ + if (strlen((char *)data + 32) != blen - 32) + goto fail; + + rune = from_string(ctx, (const char *)data + 32, data); + tal_free(data); + return rune; + +fail: + tal_free(data); + return NULL; +} + +struct rune *rune_from_base64(const tal_t *ctx, const char *str) +{ + return rune_from_base64n(ctx, str, strlen(str)); +} + +char *rune_to_base64(const tal_t *ctx, const struct rune *rune) +{ + u8 hash32[32]; + char *ret; + size_t ret_len; + struct wbuf wbuf; + + /* We're going to prepend hash */ + wbuf.off = sizeof(hash32); + wbuf.len = 64; + wbuf.buf = tal_arr(NULL, char, wbuf.len); + + to_string(&wbuf, rune, hash32); + /* Prepend hash */ + memcpy(wbuf.buf, hash32, sizeof(hash32)); + + ret = tal_arr(ctx, char, base64_encoded_length(wbuf.off) + 1); + ret_len = base64_encode_using_maps(&base64_maps_urlsafe, + ret, tal_bytelen(ret), + wbuf.buf, wbuf.off - 1); + ret[ret_len] = '\0'; + tal_free(wbuf.buf); + return ret; +} + +struct rune *rune_from_string(const tal_t *ctx, const char *str) +{ + u8 hash[32]; + if (!hex_decode(str, 64, hash, sizeof(hash))) + return NULL; + if (str[64] != ':') + return NULL; + return from_string(ctx, str + 65, hash); +} + +char *rune_to_string(const tal_t *ctx, const struct rune *rune) +{ + u8 hash32[32]; + struct wbuf wbuf; + + /* We're going to prepend hash (in hex), plus colon */ + wbuf.off = sizeof(hash32) * 2 + 1; + wbuf.len = 128; + wbuf.buf = tal_arr(ctx, char, wbuf.len); + + to_string(&wbuf, rune, hash32); + hex_encode(hash32, sizeof(hash32), wbuf.buf, sizeof(hash32) * 2 + 1); + wbuf.buf[sizeof(hash32) * 2] = ':'; + return wbuf.buf; +} diff --git a/ccan/rune/internal.h b/ccan/rune/internal.h new file mode 100644 index 00000000..e4de06cc --- /dev/null +++ b/ccan/rune/internal.h @@ -0,0 +1,8 @@ +#ifndef CCAN_RUNE_INTERNAL_H +#define CCAN_RUNE_INTERNAL_H +/* MIT (BSD) license - see LICENSE file for details */ +void rune_sha256_endmarker(struct sha256_ctx *shactx); +void rune_sha256_add_restr(struct sha256_ctx *shactx, + struct rune_restr *restr); +bool runestr_eq(const char *a, const char *b); +#endif /* CCAN_RUNE_INTERNAL_H */ diff --git a/ccan/rune/rune.c b/ccan/rune/rune.c new file mode 100644 index 00000000..84296c66 --- /dev/null +++ b/ccan/rune/rune.c @@ -0,0 +1,491 @@ +/* MIT (BSD) license - see LICENSE file for details */ +#include "config.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Helper to produce an id field */ +static struct rune_restr *unique_id_restr(const tal_t *ctx, + const char *unique_id, + const char *version) +{ + const char *id; + struct rune_restr *restr; + + assert(!strchr(unique_id, '-')); + if (version) + id = tal_fmt(NULL, "%s-%s", unique_id, version); + else + id = tal_strdup(NULL, unique_id); + + restr = rune_restr_new(ctx); + /* We use the empty field for this, since it's always present. */ + rune_restr_add_altern(restr, + take(rune_altern_new(NULL, "", '=', take(id)))); + return restr; +} + +/* We pad between fields with something identical to the SHA end marker */ +void rune_sha256_endmarker(struct sha256_ctx *shactx) +{ + static const unsigned char pad[64] = {0x80}; + be64 sizedesc; + + sizedesc = cpu_to_be64((uint64_t)shactx->bytes << 3); + /* Add '1' bit to terminate, then all 0 bits, up to next block - 8. */ + sha256_update(shactx, pad, 1 + ((128 - 8 - (shactx->bytes % 64) - 1) % 64)); + /* Add number of bits of data (big endian) */ + sha256_update(shactx, &sizedesc, 8); +} + +struct rune *rune_new(const tal_t *ctx, const u8 *secret, size_t secret_len, + const char *version) +{ + struct rune *rune = tal(ctx, struct rune); + assert(secret_len + 1 + 8 <= 64); + + if (version) + rune->version = tal_strdup(rune, version); + else + rune->version = NULL; + rune->unique_id = NULL; + sha256_init(&rune->shactx); + sha256_update(&rune->shactx, secret, secret_len); + rune_sha256_endmarker(&rune->shactx); + rune->restrs = tal_arr(rune, struct rune_restr *, 0); + return rune; +} + +struct rune *rune_dup(const tal_t *ctx, const struct rune *rune TAKES) +{ + struct rune *dup; + + if (taken(rune)) + return tal_steal(ctx, (struct rune *)rune); + + dup = tal_dup(ctx, struct rune, rune); + dup->restrs = tal_arr(dup, struct rune_restr *, tal_count(rune->restrs)); + for (size_t i = 0; i < tal_count(rune->restrs); i++) { + dup->restrs[i] = rune_restr_dup(dup->restrs, + rune->restrs[i]); + } + return dup; +} + +struct rune *rune_derive_start(const tal_t *ctx, + const struct rune *master, + const char *unique_id TAKES) +{ + struct rune *rune = rune_dup(ctx, master); + + /* If they provide a unique_id, it goes first. */ + if (unique_id) { + if (taken(unique_id)) + rune->unique_id = tal_steal(rune, unique_id); + else + rune->unique_id = tal_strdup(rune, unique_id); + + rune_add_restr(rune, take(unique_id_restr(NULL, + rune->unique_id, + rune->version))); + } else { + assert(!rune->version); + } + return rune; +} + +struct rune_altern *rune_altern_new(const tal_t *ctx, + const char *fieldname TAKES, + enum rune_condition condition, + const char *value TAKES) +{ + struct rune_altern *altern = tal(ctx, struct rune_altern); + altern->condition = condition; + altern->fieldname = tal_strdup(altern, fieldname); + altern->value = tal_strdup(altern, value); + return altern; +} + +struct rune_altern *rune_altern_dup(const tal_t *ctx, + const struct rune_altern *altern TAKES) +{ + struct rune_altern *dup; + + if (taken(altern)) + return tal_steal(ctx, (struct rune_altern *)altern); + dup = tal(ctx, struct rune_altern); + dup->condition = altern->condition; + dup->fieldname = tal_strdup(dup, altern->fieldname); + dup->value = tal_strdup(dup, altern->value); + return dup; +} + +struct rune_restr *rune_restr_dup(const tal_t *ctx, + const struct rune_restr *restr TAKES) +{ + struct rune_restr *dup; + size_t num_altern; + + if (taken(restr)) + return tal_steal(ctx, (struct rune_restr *)restr); + + num_altern = tal_count(restr->alterns); + dup = tal(ctx, struct rune_restr); + dup->alterns = tal_arr(dup, struct rune_altern *, num_altern); + for (size_t i = 0; i < num_altern; i++) { + dup->alterns[i] = rune_altern_dup(dup->alterns, + restr->alterns[i]); + } + return dup; +} + +struct rune_restr *rune_restr_new(const tal_t *ctx) +{ + struct rune_restr *restr = tal(ctx, struct rune_restr); + restr->alterns = tal_arr(restr, struct rune_altern *, 0); + return restr; +} + +void rune_restr_add_altern(struct rune_restr *restr, + const struct rune_altern *alt TAKES) +{ + size_t num = tal_count(restr->alterns); + + tal_resize(&restr->alterns, num+1); + restr->alterns[num] = rune_altern_dup(restr->alterns, alt); +} + +static bool is_unique_id(const struct rune_altern *alt) +{ + return streq(alt->fieldname, ""); +} + +/* Return unique_id if valid, and sets *version */ +static const char *extract_unique_id(const tal_t *ctx, + const struct rune_altern *alt, + const char **version) +{ + size_t len; + /* Condition must be '='! */ + if (alt->condition != '=') + return NULL; + + len = strcspn(alt->value, "-"); + if (alt->value[len]) + *version = tal_strdup(ctx, alt->value + len + 1); + else + *version = NULL; + return tal_strndup(ctx, alt->value, len); +} + +bool rune_add_restr(struct rune *rune, + const struct rune_restr *restr TAKES) +{ + size_t num = tal_count(rune->restrs); + + /* An empty fieldname is additional correctness checks */ + for (size_t i = 0; i < tal_count(restr->alterns); i++) { + if (!is_unique_id(restr->alterns[i])) + continue; + + /* Must be the only alternative */ + if (tal_count(restr->alterns) != 1) + goto fail; + /* Must be the first restriction */ + if (num != 0) + goto fail; + + rune->unique_id = extract_unique_id(rune, + restr->alterns[i], + &rune->version); + if (!rune->unique_id) + goto fail; + } + + tal_resize(&rune->restrs, num+1); + rune->restrs[num] = rune_restr_dup(rune->restrs, restr); + + rune_sha256_add_restr(&rune->shactx, rune->restrs[num]); + return true; + +fail: + if (taken(restr)) + tal_free(restr); + return false; +} + +static const char *rune_restr_test(const tal_t *ctx, + const struct rune *rune, + const struct rune_restr *restr, + const char *(*check)(const tal_t *ctx, + const struct rune *rune, + const struct rune_altern *alt, + void *arg), + void *arg) +{ + size_t num = tal_count(restr->alterns); + const char **errs = tal_arr(NULL, const char *, num); + char *err; + + /* Only one alternative has to pass! */ + for (size_t i = 0; i < num; i++) { + errs[i] = check(errs, rune, restr->alterns[i], arg); + if (!errs[i]) { + tal_free(errs); + return NULL; + } + } + + err = tal_fmt(ctx, "%s", errs[0]); + for (size_t i = 1; i < num; i++) + tal_append_fmt(&err, " AND %s", errs[i]); + tal_free(errs); + return err; +} + +static const char *cond_test(const tal_t *ctx, + const struct rune_altern *alt, + const char *complaint, + bool cond) +{ + if (cond) + return NULL; + + return tal_fmt(ctx, "%s %s %s", alt->fieldname, complaint, alt->value); +} + +static const char *integer_compare_valid(const tal_t *ctx, + const s64 *fieldval_int, + const struct rune_altern *alt, + s64 *runeval_int) +{ + long l; + char *p; + + if (!fieldval_int) + return tal_fmt(ctx, "%s is not an integer field", + alt->fieldname); + + errno = 0; + l = strtol(alt->value, &p, 10); + if (p == alt->value + || *p + || ((l == LONG_MIN || l == LONG_MAX) && errno == ERANGE)) + return tal_fmt(ctx, "%s is not a valid integer", alt->value); + + *runeval_int = l; + return NULL; +} + +static int lexo_order(const char *fieldval_str, + size_t fieldval_strlen, + const char *alt) +{ + int ret = strncmp(fieldval_str, alt, fieldval_strlen); + + /* If alt is same but longer, fieldval is < */ + if (ret == 0 && strlen(alt) > fieldval_strlen) + ret = -1; + return ret; +} + +static const char *rune_alt_single(const tal_t *ctx, + const struct rune_altern *alt, + const char *fieldval_str, + size_t fieldval_strlen, + const s64 *fieldval_int) +{ + char strfield[STR_MAX_CHARS(s64) + 1]; + s64 runeval_int = 0 /* gcc v9.4.0 gets upset with uninitiaized var at -O3 */; + const char *err; + + /* Caller can't set both! */ + if (fieldval_int) { + assert(!fieldval_str); + sprintf(strfield, "%"PRIi64, *fieldval_int); + fieldval_str = strfield; + fieldval_strlen = strlen(strfield); + } + + switch (alt->condition) { + case RUNE_COND_IF_MISSING: + if (!fieldval_str) + return NULL; + return tal_fmt(ctx, "%s is present", alt->fieldname); + case RUNE_COND_EQUAL: + if (!fieldval_str) + return tal_fmt(ctx, "%s not present", alt->fieldname); + return cond_test(ctx, alt, "is not equal to", + memeqstr(fieldval_str, fieldval_strlen, alt->value)); + case RUNE_COND_NOT_EQUAL: + if (!fieldval_str) + return tal_fmt(ctx, "%s not present", alt->fieldname); + return cond_test(ctx, alt, "is equal to", + !memeqstr(fieldval_str, fieldval_strlen, alt->value)); + case RUNE_COND_BEGINS: + if (!fieldval_str) + return tal_fmt(ctx, "%s not present", alt->fieldname); + return cond_test(ctx, alt, "does not start with", + memstarts_str(fieldval_str, fieldval_strlen, alt->value)); + case RUNE_COND_ENDS: + if (!fieldval_str) + return tal_fmt(ctx, "%s not present", alt->fieldname); + return cond_test(ctx, alt, "does not end with", + memends_str(fieldval_str, fieldval_strlen, alt->value)); + case RUNE_COND_CONTAINS: + if (!fieldval_str) + return tal_fmt(ctx, "%s not present", alt->fieldname); + return cond_test(ctx, alt, "does not contain", + memmem(fieldval_str, fieldval_strlen, + alt->value, strlen(alt->value))); + case RUNE_COND_INT_LESS: + err = integer_compare_valid(ctx, fieldval_int, + alt, &runeval_int); + if (err) + return err; + return cond_test(ctx, alt, "is greater or equal to", + *fieldval_int < runeval_int); + case RUNE_COND_INT_GREATER: + err = integer_compare_valid(ctx, fieldval_int, + alt, &runeval_int); + if (err) + return err; + return cond_test(ctx, alt, "is less or equal to", + *fieldval_int > runeval_int); + case RUNE_COND_LEXO_BEFORE: + if (!fieldval_str) + return tal_fmt(ctx, "%s not present", alt->fieldname); + return cond_test(ctx, alt, "is equal to or ordered after", + lexo_order(fieldval_str, fieldval_strlen, alt->value) < 0); + case RUNE_COND_LEXO_AFTER: + if (!fieldval_str) + return tal_fmt(ctx, "%s not present", alt->fieldname); + return cond_test(ctx, alt, "is equal to or ordered before", + lexo_order(fieldval_str, fieldval_strlen, alt->value) > 0); + case RUNE_COND_COMMENT: + return NULL; + } + /* We should never create any other values! */ + abort(); +} + +const char *rune_alt_single_str(const tal_t *ctx, + const struct rune_altern *alt, + const char *fieldval_str, + size_t fieldval_strlen) +{ + return rune_alt_single(ctx, alt, fieldval_str, fieldval_strlen, NULL); +} + +const char *rune_alt_single_int(const tal_t *ctx, + const struct rune_altern *alt, + s64 fieldval_int) +{ + return rune_alt_single(ctx, alt, NULL, 0, &fieldval_int); +} + +const char *rune_alt_single_missing(const tal_t *ctx, + const struct rune_altern *alt) +{ + return rune_alt_single(ctx, alt, NULL, 0, NULL); +} + +const char *rune_meets_criteria_(const tal_t *ctx, + const struct rune *rune, + const char *(*check)(const tal_t *ctx, + const struct rune *rune, + const struct rune_altern *alt, + void *arg), + void *arg) +{ + for (size_t i = 0; i < tal_count(rune->restrs); i++) { + const char *err; + + /* Don't "check" unique id */ + if (i == 0 && is_unique_id(rune->restrs[i]->alterns[0])) + continue; + + err = rune_restr_test(ctx, rune, rune->restrs[i], check, arg); + if (err) + return err; + } + return NULL; +} + +const char *rune_test_(const tal_t *ctx, + const struct rune *master, + const struct rune *rune, + const char *(*check)(const tal_t *ctx, + const struct rune *rune, + const struct rune_altern *alt, + void *arg), + void *arg) +{ + const char *err; + + err = rune_is_derived(master, rune); + if (err) + return err; + return rune_meets_criteria_(ctx, rune, check, arg); +} + +bool rune_altern_eq(const struct rune_altern *alt1, + const struct rune_altern *alt2) +{ + return alt1->condition == alt2->condition + && streq(alt1->fieldname, alt2->fieldname) + && streq(alt1->value, alt2->value); +} + +bool rune_restr_eq(const struct rune_restr *rest1, + const struct rune_restr *rest2) +{ + if (tal_count(rest1->alterns) != tal_count(rest2->alterns)) + return false; + + for (size_t i = 0; i < tal_count(rest1->alterns); i++) + if (!rune_altern_eq(rest1->alterns[i], rest2->alterns[i])) + return false; + return true; +} + +/* Equal, as in both NULL, or both non-NULL and matching */ +bool runestr_eq(const char *a, const char *b) +{ + if (a) { + if (!b) + return false; + return streq(a, b); + } else + return b == NULL; +} + +bool rune_eq(const struct rune *rune1, const struct rune *rune2) +{ + if (!runestr_eq(rune1->unique_id, rune2->unique_id)) + return false; + if (!runestr_eq(rune1->version, rune2->version)) + return false; + + if (memcmp(rune1->shactx.s, rune2->shactx.s, sizeof(rune1->shactx.s))) + return false; + if (rune1->shactx.bytes != rune2->shactx.bytes) + return false; + if (memcmp(rune1->shactx.buf.u8, rune2->shactx.buf.u8, + rune1->shactx.bytes % 64)) + return false; + + if (tal_count(rune1->restrs) != tal_count(rune2->restrs)) + return false; + + for (size_t i = 0; i < tal_count(rune1->restrs); i++) + if (!rune_restr_eq(rune1->restrs[i], rune2->restrs[i])) + return false; + return true; +} diff --git a/ccan/rune/rune.h b/ccan/rune/rune.h new file mode 100644 index 00000000..c373269a --- /dev/null +++ b/ccan/rune/rune.h @@ -0,0 +1,400 @@ +/* MIT (BSD) license - see LICENSE file for details */ +#ifndef CCAN_RUNE_RUNE_H +#define CCAN_RUNE_RUNE_H +#include +#include +#include +#include + +/* A rune is a series of restrictions. */ +struct rune { + /* unique_id (if any) */ + const char *unique_id; + /* Version (if any) */ + const char *version; + + /* SHA-2 256 of restrictions so far. */ + struct sha256_ctx shactx; + /* Length given by tal_count() */ + struct rune_restr **restrs; +}; + +/* A restriction is one or more alternatives (altern) */ +struct rune_restr { + /* Length given by tal_count() */ + struct rune_altern **alterns; +}; + +enum rune_condition { + RUNE_COND_IF_MISSING = '!', + RUNE_COND_EQUAL = '=', + RUNE_COND_NOT_EQUAL = '/', + RUNE_COND_BEGINS = '^', + RUNE_COND_ENDS = '$', + RUNE_COND_CONTAINS = '~', + RUNE_COND_INT_LESS = '<', + RUNE_COND_INT_GREATER = '>', + RUNE_COND_LEXO_BEFORE = '{', + RUNE_COND_LEXO_AFTER = '}', + RUNE_COND_COMMENT = '#', +}; + +/* An alternative is a utf-8 fieldname, a condition, and a value */ +struct rune_altern { + enum rune_condition condition; + /* Strings. */ + const char *fieldname, *value; +}; + +/** + * rune_new - Create an unrestricted rune from this secret. + * @ctx: tal context, or NULL. Freeing @ctx will free the returned rune. + * @secret: secret bytes. + * @secret_len: number of @secret bytes (must be 55 bytes or less) + * @version: if non-NULL, sets a version for this rune. + * + * This allocates a new, unrestricted rune (sometimes called a master rune). + * + * Setting a version allows for different interpretations of a rune if + * things change in future, at cost of some space when it's used. + * + * Example: + * u8 secret[16]; + * struct rune *master; + * + * // A secret determined with a fair die roll! + * memset(secret, 5, sizeof(secret)); + * master = rune_new(NULL, secret, sizeof(secret), NULL); + * assert(master); + */ +struct rune *rune_new(const tal_t *ctx, const u8 *secret, size_t secret_len, + const char *version); + +/** + * rune_derive_start - Copy master rune, add a unique id. + * @ctx: context to allocate rune off + * @master: master rune. + * @unique_id: unique id; can be NULL, but that's not recommended. + * + * It's usually recommended to assign each rune a unique_id, so that + * specific runes can be blacklisted later (otherwise you need to disable + * all runes). This enlarges the rune string by '=' however. + * + * The rune version will be the same as the master: if that's non-zero, + * you *must* set unique_id. + * + * @unique_id cannot contain '-'. + * + * Example: + * struct rune *rune; + * // In reality, some global incrementing variable. + * const char *id = "1"; + * rune = rune_derive_start(NULL, master, id); + * assert(rune); + */ +struct rune *rune_derive_start(const tal_t *ctx, + const struct rune *master, + const char *unique_id); + +/** + * rune_dup - Copy a rune. + * @ctx: tal context, or NULL. + * @altern: the altern to copy. + * + * If @altern is take(), then simply returns it, otherwise copies. + */ +struct rune *rune_dup(const tal_t *ctx, const struct rune *rune TAKES); + +/** + * rune_altern_new - Create a new alternative. + * @ctx: tal context, or NULL. Freeing @ctx will free the returned altern. + * @fieldname: the UTF-8 field for the altern. You can only have + * alphanumerics, '.', '-' and '_' here. + * @condition: the condition, defined above. + * @value: the value for comparison; use "" if you don't care. Any UTF-8 value + * is allowed. + * + * An altern is the basis of rune restrictions (technically, a restriction + * is one or more alterns, but it's often just one). + * + * Example: + * struct rune_altern *a1, *a2; + * a1 = rune_altern_new(NULL, "val", RUNE_COND_EQUAL, "7"); + * a2 = rune_altern_new(NULL, "val2", '>', "-1"); + * assert(a1 && a2); + */ +struct rune_altern *rune_altern_new(const tal_t *ctx, + const char *fieldname TAKES, + enum rune_condition condition, + const char *value TAKES); + +/** + * rune_altern_dup - copy an alternative. + * @ctx: tal context, or NULL. + * @altern: the altern to copy. + * + * If @altern is take(), then simply returns it, otherwise copies. + */ +struct rune_altern *rune_altern_dup(const tal_t *ctx, + const struct rune_altern *altern TAKES); + +/** + * rune_restr_new - Create a new (empty) restriction. + * @ctx: tal context, or NULL. Freeing @ctx will free the returned restriction. + * + * Example: + * struct rune_restr *restr = rune_restr_new(NULL); + * assert(restr); + */ +struct rune_restr *rune_restr_new(const tal_t *ctx); + +/** + * rune_restr_dup - copy a restr. + * @ctx: tal context, or NULL. + * @restr: the restr to copy. + * + * If @resttr is take(), then simply returns it, otherwise copies. + */ +struct rune_restr *rune_restr_dup(const tal_t *ctx, + const struct rune_restr *restr TAKES); + +/** + * rune_restr_add_altern - add an altern to this restriction + * @restr: the restriction to add to + * @alt: the altern. + * + * If the alt is take(alt) then the alt will be owned by the restriction, + * otherwise it's copied. + * + * Example: + * rune_restr_add_altern(restr, take(a1)); + * rune_restr_add_altern(restr, take(a2)); + */ +void rune_restr_add_altern(struct rune_restr *restr, + const struct rune_altern *alt TAKES); + +/** + * rune_add_restr - add a restriction to this rune + * @rune: the rune to add to. + * @restr: the (non-empty) restriction. + * + * If the alt is take(alt) then the alt will be owned by the restr, + * otherwise it's copied (and all its children are copied!). + * + * This fails (and returns false) if restr tries to set unique_id/version + * and is not the first restriction, or has more than one alternative, + * or uses a non '=' condition. + * + * Example: + * rune_add_restr(rune, take(restr)); + */ +bool rune_add_restr(struct rune *rune, + const struct rune_restr *restr TAKES); + +/** + * rune_altern_eq - are two rune_altern equivalent? + * @alt1: the first + * @alt2: the second + */ +bool rune_altern_eq(const struct rune_altern *alt1, + const struct rune_altern *alt2); + +/** + * rune_restr_eq - are two rune_restr equivalent? + * @rest1: the first + * @rest2: the second + */ +bool rune_restr_eq(const struct rune_restr *rest1, + const struct rune_restr *rest2); + +/** + * rune_eq - are two runes equivalent? + * @rest1: the first + * @rest2: the second + */ +bool rune_eq(const struct rune *rune1, const struct rune *rune2); + +/** + * rune_alt_single_str - helper to implement check(). + * @ctx: context to allocate any error return from. + * @alt: alternative to test. + * @fieldval_str: field value as a string. + * @fieldval_strlen: length of @fieldval_str + */ +const char *rune_alt_single_str(const tal_t *ctx, + const struct rune_altern *alt, + const char *fieldval_str, + size_t fieldval_strlen); + +/** + * rune_alt_single_int - helper to implement check(). + * @ctx: context to allocate any error return from. + * @alt: alternative to test. + * @fieldval_int: field value as an integer. + */ +const char *rune_alt_single_int(const tal_t *ctx, + const struct rune_altern *alt, + s64 fieldval_int); + +/** + * rune_alt_single_missing - helper to implement check(). + * @ctx: context to allocate any error return from. + * @alt: alternative to test. + * + * Use this if alt->fieldname is unknown (it could still pass, if + * the test is that the fieldname is missing). + */ +const char *rune_alt_single_missing(const tal_t *ctx, + const struct rune_altern *alt); + + +/** + * rune_is_derived - is a rune derived from this other rune? + * @source: the base rune (usually the master rune) + * @rune: the rune to check. + * + * This is the first part of "is this rune valid?": does the cryptography + * check out, such that they validly made the rune from this source rune? + * + * It also checks that the versions match: if you want to allow more than + * one version, see rune_is_derived_anyversion. + */ +const char *rune_is_derived(const struct rune *source, const struct rune *rune); + +/** + * rune_is_derived_anyversion - is a rune derived from this other rune? + * @source: the base rune (usually the master rune) + * @rune: the rune to check. + * + * This does not check source->version against rune->version: if you issue + * different rune versions you will need to check that yourself. + */ +const char *rune_is_derived_anyversion(const struct rune *source, + const struct rune *rune); + +/** + * rune_meets_criteria - do we meet the criteria specified by the rune? + * @ctx: the tal context to allocate the returned error off. + * @rune: the rune to check. + * @check: the callback to check values + * @arg: data to hand to @check + * + * This is the second part of "is this rune valid?". + */ +const char *rune_meets_criteria_(const tal_t *ctx, + const struct rune *rune, + const char *(*check)(const tal_t *ctx, + const struct rune *rune, + const struct rune_altern *alt, + void *arg), + void *arg); + +/* Typesafe wrapper */ +#define rune_meets_criteria(ctx, rune, check, arg) \ + rune_meets_criteria_(typesafe_cb_preargs(const char *, void *, \ + (ctx), (rune), \ + (check), (arg), \ + const tal_t *, \ + const struct rune *, \ + const struct rune_altern *), \ + (arg)) + +/** + * rune_test - is a rune authorized? + * @ctx: the tal context to allocate @errstr off. + * @master: the master rune created from secret. + * @rune: the rune to check. + * @errstr: if non-NULL, descriptive string of failure. + * @get: the callback to get values + * @arg: data to hand to callback + * + * Simple call for rune_is_derived() and rune_meets_criteria(). If + * it's not OK, returns non-NULL. + */ +const char *rune_test_(const tal_t *ctx, + const struct rune *master, + const struct rune *rune, + const char *(*check)(const tal_t *ctx, + const struct rune *rune, + const struct rune_altern *alt, + void *arg), + void *arg); + +/* Typesafe wrapper */ +#define rune_test(ctx_, master_, rune_, check_, arg_) \ + rune_test_((ctx_), (master_), (rune_), \ + typesafe_cb_preargs(const char *, void *, \ + (check_), (arg_), \ + const tal_t *, \ + const struct rune *, \ + const struct rune_altern *), \ + (arg_)) + + +/** + * rune_from_base64 - convert base64 string to rune. + * @ctx: context to allocate rune off. + * @str: base64 string. + * + * Returns NULL if it's malformed. + */ +struct rune *rune_from_base64(const tal_t *ctx, const char *str); + +/** + * rune_from_base64n - convert base64 string to rune. + * @ctx: context to allocate rune off. + * @str: base64 string. + * @len: length of @str. + * + * Returns NULL if it's malformed. + */ +struct rune *rune_from_base64n(const tal_t *ctx, const char *str, size_t len); + +/** + * rune_to_base64 - convert run to base64 string. + * @ctx: context to allocate rune off. + * @rune: the rune. + * + * Only returns NULL if you've allowed tal allocations to return NULL. + */ +char *rune_to_base64(const tal_t *ctx, const struct rune *rune); + +/** + * This is a much more convenient working form. + */ +struct rune *rune_from_string(const tal_t *ctx, const char *str); +char *rune_to_string(const tal_t *ctx, const struct rune *rune); + +/** + * rune_restr_from_string - convenience routine to parse a single restriction. + * @ctx: context to allocate rune off. + * @str: the string of form "[|]*" + * @len: the length of @str. + * + * This is useful for writing simple tests and making simple runes. + */ +struct rune_restr *rune_restr_from_string(const tal_t *ctx, + const char *str, + size_t len); + +/** + * rune_condition_is_valid: is this a valid condition? + * @cond: potential condition character. + * + * Returns true if it's one of enum rune_condition. + */ +bool rune_condition_is_valid(enum rune_condition cond); + +/** + * rune_altern_fieldname_len: how much of this string is condition? + * @alternstr: potential alternative string + * @alternstrlen: length + * + * This helps parsing your own runes. + * + * Returns the first possible condition (check with rune_condition_is_valid) + * or alternstrlen if none found. + */ +size_t rune_altern_fieldname_len(const char *alternstr, size_t alternstrlen); + +#endif /* CCAN_RUNE_RUNE_H */ diff --git a/ccan/rune/test/run-alt-lexicographic-order.c b/ccan/rune/test/run-alt-lexicographic-order.c new file mode 100644 index 00000000..a37ee581 --- /dev/null +++ b/ccan/rune/test/run-alt-lexicographic-order.c @@ -0,0 +1,33 @@ +#include +#include +#include +#include + +int main(void) +{ + const char *str = "test string"; + plan_tests(strlen(str) * strlen(str)); + + for (size_t i = 0; str[i]; i++) { + char *stra = strdup(str); + stra[i] = '\0'; + for (size_t j = 0; str[j]; j++) { + char *strb = strdup(str); + strb[j] = '\0'; + int lexo, strc; + + lexo = lexo_order(str, i, strb); + strc = strcmp(stra, strb); + if (strc > 0) + ok1(lexo > 0); + else if (strc < 0) + ok1(lexo < 0); + else + ok1(lexo == 0); + free(strb); + } + free(stra); + } + /* This exits depending on whether all tests passed */ + return exit_status(); +} diff --git a/ccan/rune/test/run-altern-escape.c b/ccan/rune/test/run-altern-escape.c new file mode 100644 index 00000000..550cafae --- /dev/null +++ b/ccan/rune/test/run-altern-escape.c @@ -0,0 +1,37 @@ +#include +#include +#include +#include +#include + +int main(void) +{ + static const u8 secret_zero[16]; + struct rune *rune; + struct rune_restr *restr; + const tal_t *ctx = tal(NULL, char); + + plan_tests(9); + restr = rune_restr_from_string(ctx, "desc=@tipjar\\|jb55@sendsats.lol", + strlen("desc=@tipjar\\|jb55@sendsats.lol")); + ok1(tal_count(restr->alterns) == 1); + ok1(restr->alterns[0]->condition == '='); + ok1(streq(restr->alterns[0]->fieldname, "desc")); + ok1(streq(restr->alterns[0]->value, "@tipjar|jb55@sendsats.lol")); + + rune = rune_new(ctx, secret_zero, sizeof(secret_zero), NULL); + rune_add_restr(rune, take(restr)); + + /* Converting via base64 should not change it! */ + rune = rune_from_base64(ctx, rune_to_base64(ctx, rune)); + ok1(tal_count(rune->restrs) == 1); + restr = rune->restrs[0]; + ok1(tal_count(restr->alterns) == 1); + ok1(restr->alterns[0]->condition == '='); + ok1(streq(restr->alterns[0]->fieldname, "desc")); + ok1(streq(restr->alterns[0]->value, "@tipjar|jb55@sendsats.lol")); + + tal_free(ctx); + /* This exits depending on whether all tests passed */ + return exit_status(); +} diff --git a/ccan/rune/test/run.c b/ccan/rune/test/run.c new file mode 100644 index 00000000..d90b701c --- /dev/null +++ b/ccan/rune/test/run.c @@ -0,0 +1,127 @@ +#include +#include +#include +#include +#include + +static const char *check(const tal_t *ctx, + const struct rune *rune, + const struct rune_altern *alt, + char **parts) +{ + const char *val = NULL; + + for (size_t i = 1; parts[i]; i++) { + if (strstarts(parts[i], alt->fieldname) + && parts[i][strlen(alt->fieldname)] == '=') + val = parts[i] + strlen(alt->fieldname) + 1; + } + + /* If it's an integer, hand it like that */ + if (val) { + char *endp; + s64 v = strtol(val, &endp, 10); + if (*endp == '\0' && endp != val) + return rune_alt_single_int(ctx, alt, v); + return rune_alt_single_str(ctx, alt, val, strlen(val)); + } + return rune_alt_single_missing(ctx, alt); +} + +int main(void) +{ + char *vecs; + char **lines; + static const u8 secret_zero[16]; + struct rune *mr; + + /* Test vector rune uses all-zero secret */ + mr = rune_new(NULL, secret_zero, sizeof(secret_zero), NULL); + + /* Python runes library generates test vectors */ + vecs = grab_file(mr, "test/test_vectors.csv"); + assert(vecs); + lines = tal_strsplit(mr, take(vecs), "\n", STR_NO_EMPTY); + + plan_tests(355); + + for (size_t i = 0; lines[i]; i++) { + struct rune *rune1, *rune2; + char **parts; + + parts = tal_strsplit(lines, lines[i], ",", STR_EMPTY_OK); + if (streq(parts[0], "VALID")) { + diag("test %s %s", parts[0], parts[1]); + rune1 = rune_from_string(parts, parts[2]); + ok1(rune1); + rune2 = rune_from_base64(parts, parts[3]); + ok1(rune2); + ok1(rune_eq(rune1, rune2)); + ok1(streq(rune_to_string(parts, rune2), parts[2])); + ok1(streq(rune_to_base64(parts, rune1), parts[3])); + ok1(rune_is_derived_anyversion(mr, rune1) == NULL); + ok1(rune_is_derived_anyversion(mr, rune2) == NULL); + + if (parts[4]) { + if (parts[5]) + ok1(streq(rune1->version, parts[5])); + ok1(streq(rune1->unique_id, parts[4])); + } else { + ok1(!rune1->version); + ok1(!rune1->unique_id); + } + mr->version = NULL; + } else if (streq(parts[0], "DERIVE")) { + struct rune_restr *restr; + diag("test %s %s", parts[0], parts[1]); + rune1 = rune_from_base64(parts, parts[2]); + ok1(rune1); + rune2 = rune_from_base64(parts, parts[3]); + ok1(rune2); + ok1(rune_is_derived_anyversion(mr, rune1) == NULL); + ok1(rune_is_derived_anyversion(mr, rune2) == NULL); + ok1(rune_is_derived_anyversion(rune1, rune2) == NULL); + + restr = rune_restr_new(NULL); + for (size_t j = 4; parts[j]; j+=3) { + struct rune_altern *alt; + alt = rune_altern_new(NULL, + parts[j], + parts[j+1][0], + parts[j+2]); + rune_restr_add_altern(restr, take(alt)); + } + rune_add_restr(rune1, take(restr)); + ok1(rune_eq(rune1, rune2)); + } else if (streq(parts[0], "MALFORMED")) { + diag("test %s %s", parts[0], parts[1]); + rune1 = rune_from_string(parts, parts[2]); + ok1(!rune1); + rune2 = rune_from_base64(parts, parts[3]); + ok1(!rune2); + } else if (streq(parts[0], "BAD DERIVATION")) { + diag("test %s %s", parts[0], parts[1]); + rune1 = rune_from_string(parts, parts[2]); + ok1(rune1); + rune2 = rune_from_base64(parts, parts[3]); + ok1(rune2); + ok1(rune_eq(rune1, rune2)); + ok1(rune_is_derived(mr, rune1) != NULL); + ok1(rune_is_derived(mr, rune2) != NULL); + } else { + const char *err; + diag("test %s", parts[0]); + err = rune_test(parts, mr, rune1, check, parts); + if (streq(parts[0], "PASS")) { + ok1(!err); + } else { + assert(streq(parts[0], "FAIL")); + ok1(err); + } + } + } + + tal_free(mr); + /* This exits depending on whether all tests passed */ + return exit_status(); +} diff --git a/ccan/rune/test/test_vectors.csv b/ccan/rune/test/test_vectors.csv new file mode 100644 index 00000000..880ea3c6 --- /dev/null +++ b/ccan/rune/test/test_vectors.csv @@ -0,0 +1,156 @@ +VALID,empty rune (secret = [0]*16),374708fff7719dd5979ec875d56cd2286f6d3cf7ec317a3b25632aab28ec37bb:,N0cI__dxndWXnsh11WzSKG9tPPfsMXo7JWMqqyjsN7s= +PASS +PASS,f1=1 +PASS,f1=var +PASS,f1=\|\&\\ +VALID,unique id 1,6035731a2cbb022cbeb67645aa0f8a26653d8cc454e0e087d4d19d282b8da4bd:=1,YDVzGiy7Aiy-tnZFqg-KJmU9jMRU4OCH1NGdKCuNpL09MQ==,1 +VALID,unique id 2 version 1,4520773407c9658646326fdffe685ffbc3c8639a080dae4310b371830a205cf1:=2-1,RSB3NAfJZYZGMm_f_mhf-8PIY5oIDa5DELNxgwogXPE9Mi0x,2,1 +VALID,f1 is missing,64a926b7185d7cf98e10a07dfc4e83d2a826896ebdb112ac964566fa2d50b464:f1!,ZKkmtxhdfPmOEKB9_E6D0qgmiW69sRKslkVm-i1QtGRmMSE= +PASS +PASS,f2=f1 +FAIL,f1=1 +FAIL,f1=var +VALID,f1 equals v1,745c6e39cd41ee9f8388af8ad882bae4ee4e8f6b373f7682cc64d8574551fa5f:f1=v1,dFxuOc1B7p-DiK-K2IK65O5Oj2s3P3aCzGTYV0VR-l9mMT12MQ== +PASS,f1=v1 +FAIL,f1=v +FAIL,f1=v1a +FAIL +FAIL,f2=f1 +VALID,f1 not equal v1,c9236a6532bfa8e24bec9a66e96af3fb355f817770e79c5a81f6dd0b5ed20e47:f1/v1,ySNqZTK_qOJL7Jpm6Wrz-zVfgXdw55xagfbdC17SDkdmMS92MQ== +PASS,f1=v2 +PASS,f1=v +PASS,f1=v1a +FAIL +FAIL,f2=v1 +VALID,f1 ends with v1,71f2a1ec9631efc75b01db15fe1f025327ab467f8a83e6bfa7506da222adc5a2:f1$v1,cfKh7JYx78dbAdsV_h8CUyerRn-Kg-a_p1BtoiKtxaJmMSR2MQ== +PASS,f1=v1 +PASS,f1=2v1 +FAIL,f1=v1a +FAIL +VALID,f1 starts with v1,5b13dffbbd9f7b191b0557595d10b22c0acec0c567f8efeba1d7d047927d7bce:f1^v1,WxPf-72fexkbBVdZXRCyLArOwMVn-O_rodfQR5J9e85mMV52MQ== +PASS,f1=v1 +PASS,f1=v1a +FAIL,f1=2v1 +FAIL +VALID,f1 contains v1,ccbe593b72e0ab29446e46796ccd0c775ecd7a327fcc9ddc00fd3910cdacca00:f1~v1,zL5ZO3LgqylEbkZ5bM0Md17NejJ_zJ3cAP05EM2sygBmMX52MQ== +PASS,f1=v1 +PASS,f1=v1a +PASS,f1=2v1 +PASS,f1=2v12 +FAIL,f1=1v2 +FAIL +VALID,f1 less than v1,caff52cedb9241dc00aea7cefc2b89b0a7445b1a4e34c48a5a2b91d2fe76d31f:f1v1,ITV0jxlW2d-jxbCatq-da7BqQcW8-T0_gQXLJ4r1rFZmMT52MQ== +FAIL,f1=1 +FAIL,f1=2 +FAIL,f1=v1 +FAIL +VALID,f1 greater than 1,84e9991dd941bac97cc681eefec5dd7ac3668a4490ca6b0f19f0e79d2bb9c746:f1>1,hOmZHdlBusl8xoHu_sXdesNmikSQymsPGfDnnSu5x0ZmMT4x +PASS,f1=2 +PASS,f1=10000 +FAIL,f1=1 +FAIL,f1=-10000 +FAIL,f1=0 +FAIL,f1=v1 +FAIL +VALID,f1 sorts before 11,b9653ad0dcad7e5ed183f98cdd7e616acd07a98cc66a107a67626290bf000236:f1{11,uWU60Nytfl7Rg_mM3X5has0HqYzGahB6Z2JikL8AAjZmMXsxMQ== +PASS,f1=0 +PASS,f1=1 +PASS,f1= +PASS,f1=/ +FAIL,f1=11 +FAIL,f1=111 +FAIL,f1=v1 +FAIL,f1=: +FAIL +VALID,f1 sorts after 11,8c1f6c7c39badc5dea850192a0a4c6e9dd96bf33d410adc5a08fc375b22a1a52:f1}11,jB9sfDm63F3qhQGSoKTG6d2WvzPUEK3FoI_DdbIqGlJmMX0xMQ== +PASS,f1=111 +PASS,f1=v1 +PASS,f1=: +FAIL,f1=0 +FAIL,f1=1 +FAIL,f1= +FAIL,f1=/ +FAIL,f1=11 +FAIL +VALID,f1 comment 11,76bdd625de0e12058956e6c8a07cac58d7dc2253609a6bfb959f87cc094f3f0f:f1#11,dr3WJd4OEgWJVubIoHysWNfcIlNgmmv7lZ-HzAlPPw9mMSMxMQ== +PASS,f1=111 +PASS,f1=v1 +PASS,f1=: +PASS,f1=0 +PASS,f1=1 +PASS,f1= +PASS,f1=/ +PASS,f1=11 +PASS +VALID,f_with_underscores equals v1,ee979e1f2c376d69923aab0e8e001111963af038bdce394ffd7ecdc9e7020a6e:f_with_underscores=v1,7peeHyw3bWmSOqsOjgAREZY68Di9zjlP_X7NyecCCm5mX3dpdGhfdW5kZXJzY29yZXM9djE= +PASS,f_with_underscores=v1 +FAIL,f_with_underscores=v +FAIL,f_with_underscores=v1a +FAIL +FAIL,f2=f_with_underscores +VALID,f1=1 or f2=3,85c3643dc102f0a0d6f20eeb8c294092151688fae41ef7c8ec7272ab23918376:f1=1|f2=3,hcNkPcEC8KDW8g7rjClAkhUWiPrkHvfI7HJyqyORg3ZmMT0xfGYyPTM= +PASS,f1=1 +PASS,f1=1,f2=2 +PASS,f2=3 +PASS,f1=var,f2=3 +PASS,f1=1,f2=3 +FAIL +FAIL,f1=2 +FAIL,f1=f1 +FAIL,f2=1 +FAIL,f2=f1 +DERIVE,unique_id 1 derivation,N0cI__dxndWXnsh11WzSKG9tPPfsMXo7JWMqqyjsN7s=,YDVzGiy7Aiy-tnZFqg-KJmU9jMRU4OCH1NGdKCuNpL09MQ==,,=,1 +DERIVE,unique_id 2 version 1 derivation,N0cI__dxndWXnsh11WzSKG9tPPfsMXo7JWMqqyjsN7s=,RSB3NAfJZYZGMm_f_mhf-8PIY5oIDa5DELNxgwogXPE9Mi0x,,=,2-1 +DERIVE,f1=1 or f2=3,N0cI__dxndWXnsh11WzSKG9tPPfsMXo7JWMqqyjsN7s=,hcNkPcEC8KDW8g7rjClAkhUWiPrkHvfI7HJyqyORg3ZmMT0xfGYyPTM=,f1,=,1,f2,=,3 +DERIVE,AND f3 contains &|\,hcNkPcEC8KDW8g7rjClAkhUWiPrkHvfI7HJyqyORg3ZmMT0xfGYyPTM=,S253BW1Lragb1CpCSLXYGt9AdrE4iFMlXmnO0alV5vlmMT0xfGYyPTMmZjN-XCZcfFxc,f3,~,&|\ +PASS,f1=1,f3=&|\ +PASS,f2=3,f3=&|\x +FAIL +FAIL,f1=1 +FAIL,f2=3 +FAIL,f1=1,f2=3 +FAIL,f1=2,f3=&|\ +FAIL,f2=2,f3=&|\ +FAIL,f3=&|\ +MALFORMED,unique id must use = not !,6035731a2cbb022cbeb67645aa0f8a26653d8cc454e0e087d4d19d282b8da4bd:!1,YDVzGiy7Aiy-tnZFqg-KJmU9jMRU4OCH1NGdKCuNpL0hMQ== +MALFORMED,unique id must use = not /,6035731a2cbb022cbeb67645aa0f8a26653d8cc454e0e087d4d19d282b8da4bd:/1,YDVzGiy7Aiy-tnZFqg-KJmU9jMRU4OCH1NGdKCuNpL0vMQ== +MALFORMED,unique id must use = not ^,6035731a2cbb022cbeb67645aa0f8a26653d8cc454e0e087d4d19d282b8da4bd:^1,YDVzGiy7Aiy-tnZFqg-KJmU9jMRU4OCH1NGdKCuNpL1eMQ== +MALFORMED,unique id must use = not $,6035731a2cbb022cbeb67645aa0f8a26653d8cc454e0e087d4d19d282b8da4bd:$1,YDVzGiy7Aiy-tnZFqg-KJmU9jMRU4OCH1NGdKCuNpL0kMQ== +MALFORMED,unique id must use = not ~,6035731a2cbb022cbeb67645aa0f8a26653d8cc454e0e087d4d19d282b8da4bd:~1,YDVzGiy7Aiy-tnZFqg-KJmU9jMRU4OCH1NGdKCuNpL1-MQ== +MALFORMED,unique id must use = not <,6035731a2cbb022cbeb67645aa0f8a26653d8cc454e0e087d4d19d282b8da4bd:<1,YDVzGiy7Aiy-tnZFqg-KJmU9jMRU4OCH1NGdKCuNpL08MQ== +MALFORMED,unique id must use = not >,6035731a2cbb022cbeb67645aa0f8a26653d8cc454e0e087d4d19d282b8da4bd:>1,YDVzGiy7Aiy-tnZFqg-KJmU9jMRU4OCH1NGdKCuNpL0-MQ== +MALFORMED,unique id must use = not },6035731a2cbb022cbeb67645aa0f8a26653d8cc454e0e087d4d19d282b8da4bd:}1,YDVzGiy7Aiy-tnZFqg-KJmU9jMRU4OCH1NGdKCuNpL19MQ== +MALFORMED,unique id must use = not {,6035731a2cbb022cbeb67645aa0f8a26653d8cc454e0e087d4d19d282b8da4bd:{1,YDVzGiy7Aiy-tnZFqg-KJmU9jMRU4OCH1NGdKCuNpL17MQ== +MALFORMED,unique id cannot be overridden,7a63a2966d38e6fed89256d4a6e983a6813bf084d4fc6c20b9cdaef24b23fa7e:=1-2&=3,emOilm045v7YklbUpumDpoE78ITU_Gwguc2u8ksj-n49MS0yJj0z +MALFORMED,version cannot be overridden,db823224f960976b3ee142ce8899fc7ea461b42617e7d16167b1886c5988c628:=1-2&=1-3,24IyJPlgl2s-4ULOiJn8fqRhtCYX59FhZ7GIbFmIxig9MS0yJj0xLTM= +MALFORMED,Bad condition ",76bdd625de0e12058956e6c8a07cac58d7dc2253609a6bfb959f87cc094f3f0f:f1"11,dr3WJd4OEgWJVubIoHysWNfcIlNgmmv7lZ-HzAlPPw9mMSIxMQ== +MALFORMED,Bad condition &,76bdd625de0e12058956e6c8a07cac58d7dc2253609a6bfb959f87cc094f3f0f:f1&11,dr3WJd4OEgWJVubIoHysWNfcIlNgmmv7lZ-HzAlPPw9mMSYxMQ== +MALFORMED,Bad condition ',76bdd625de0e12058956e6c8a07cac58d7dc2253609a6bfb959f87cc094f3f0f:f1'11,dr3WJd4OEgWJVubIoHysWNfcIlNgmmv7lZ-HzAlPPw9mMScxMQ== +MALFORMED,Bad condition (,76bdd625de0e12058956e6c8a07cac58d7dc2253609a6bfb959f87cc094f3f0f:f1(11,dr3WJd4OEgWJVubIoHysWNfcIlNgmmv7lZ-HzAlPPw9mMSgxMQ== +MALFORMED,Bad condition ),76bdd625de0e12058956e6c8a07cac58d7dc2253609a6bfb959f87cc094f3f0f:f1)11,dr3WJd4OEgWJVubIoHysWNfcIlNgmmv7lZ-HzAlPPw9mMSkxMQ== +MALFORMED,Bad condition *,76bdd625de0e12058956e6c8a07cac58d7dc2253609a6bfb959f87cc094f3f0f:f1*11,dr3WJd4OEgWJVubIoHysWNfcIlNgmmv7lZ-HzAlPPw9mMSoxMQ== +MALFORMED,Bad condition +,76bdd625de0e12058956e6c8a07cac58d7dc2253609a6bfb959f87cc094f3f0f:f1+11,dr3WJd4OEgWJVubIoHysWNfcIlNgmmv7lZ-HzAlPPw9mMSsxMQ== +MALFORMED,Bad condition -,76bdd625de0e12058956e6c8a07cac58d7dc2253609a6bfb959f87cc094f3f0f:f1-11,dr3WJd4OEgWJVubIoHysWNfcIlNgmmv7lZ-HzAlPPw9mMS0xMQ== +MALFORMED,Bad condition .,76bdd625de0e12058956e6c8a07cac58d7dc2253609a6bfb959f87cc094f3f0f:f1.11,dr3WJd4OEgWJVubIoHysWNfcIlNgmmv7lZ-HzAlPPw9mMS4xMQ== +MALFORMED,Bad condition :,76bdd625de0e12058956e6c8a07cac58d7dc2253609a6bfb959f87cc094f3f0f:f1:11,dr3WJd4OEgWJVubIoHysWNfcIlNgmmv7lZ-HzAlPPw9mMToxMQ== +MALFORMED,Bad condition ;,76bdd625de0e12058956e6c8a07cac58d7dc2253609a6bfb959f87cc094f3f0f:f1;11,dr3WJd4OEgWJVubIoHysWNfcIlNgmmv7lZ-HzAlPPw9mMTsxMQ== +MALFORMED,Bad condition ?,76bdd625de0e12058956e6c8a07cac58d7dc2253609a6bfb959f87cc094f3f0f:f1?11,dr3WJd4OEgWJVubIoHysWNfcIlNgmmv7lZ-HzAlPPw9mMT8xMQ== +MALFORMED,Bad condition [,76bdd625de0e12058956e6c8a07cac58d7dc2253609a6bfb959f87cc094f3f0f:f1[11,dr3WJd4OEgWJVubIoHysWNfcIlNgmmv7lZ-HzAlPPw9mMVsxMQ== +MALFORMED,Bad condition \,76bdd625de0e12058956e6c8a07cac58d7dc2253609a6bfb959f87cc094f3f0f:f1\11,dr3WJd4OEgWJVubIoHysWNfcIlNgmmv7lZ-HzAlPPw9mMVwxMQ== +MALFORMED,Bad condition ],76bdd625de0e12058956e6c8a07cac58d7dc2253609a6bfb959f87cc094f3f0f:f1]11,dr3WJd4OEgWJVubIoHysWNfcIlNgmmv7lZ-HzAlPPw9mMV0xMQ== +MALFORMED,Bad condition `,76bdd625de0e12058956e6c8a07cac58d7dc2253609a6bfb959f87cc094f3f0f:f1`11,dr3WJd4OEgWJVubIoHysWNfcIlNgmmv7lZ-HzAlPPw9mMWAxMQ== +MALFORMED,Bad condition |,76bdd625de0e12058956e6c8a07cac58d7dc2253609a6bfb959f87cc094f3f0f:f1|11,dr3WJd4OEgWJVubIoHysWNfcIlNgmmv7lZ-HzAlPPw9mMXwxMQ== +BAD DERIVATION,Incremented sha,76bdd625de0e12058956e6c8a07cac58d7dc2253609a6bfb959f87cc094f3f0e:f1#11,dr3WJd4OEgWJVubIoHysWNfcIlNgmmv7lZ-HzAlPPw5mMSMxMQ== +BAD DERIVATION,Unchanged sha,76bdd625de0e12058956e6c8a07cac58d7dc2253609a6bfb959f87cc094f3f0f:f1#11&a=1,dr3WJd4OEgWJVubIoHysWNfcIlNgmmv7lZ-HzAlPPw9mMSMxMSZhPTE= diff --git a/ccan/str/base32/base32.c b/ccan/str/base32/base32.c index 71ca87d5..6145da30 100644 --- a/ccan/str/base32/base32.c +++ b/ccan/str/base32/base32.c @@ -70,7 +70,8 @@ static bool decode_8_chars(const char c[8], beint64_t *res, int *bytes) { uint64_t acc = 0; size_t num_pad = 0; - for (int i = 0; i < 8; i++) { + int i; + for (i = 0; i < 8; i++) { const char *p; acc <<= 5; diff --git a/ccan/stringbuilder/stringbuilder.c b/ccan/stringbuilder/stringbuilder.c index d34de811..8eb4ab42 100644 --- a/ccan/stringbuilder/stringbuilder.c +++ b/ccan/stringbuilder/stringbuilder.c @@ -22,7 +22,8 @@ static int stringbuilder_cpy( if (*str != s) { if (!s_len) s_len = strlen(s); - if (s_len > *str_sz) + /* Include nul term! */ + if (s_len >= *str_sz) return EMSGSIZE; strcpy(*str, s); } diff --git a/ccan/strmap/strmap.c b/ccan/strmap/strmap.c index 9fa51d0d..16a30e03 100644 --- a/ccan/strmap/strmap.c +++ b/ccan/strmap/strmap.c @@ -17,9 +17,8 @@ struct node { }; /* Closest member to this in a non-empty map. */ -static struct strmap *closest(struct strmap *n, const char *member) +static struct strmap *closest(struct strmap *n, const char *member, size_t len) { - size_t len = strlen(member); const u8 *bytes = (const u8 *)member; /* Anything with NULL value is a node. */ @@ -35,20 +34,26 @@ static struct strmap *closest(struct strmap *n, const char *member) return n; } -void *strmap_get_(const struct strmap *map, const char *member) +void *strmap_getn_(const struct strmap *map, + const char *member, size_t memberlen) { struct strmap *n; /* Not empty map? */ if (map->u.n) { - n = closest((struct strmap *)map, member); - if (streq(member, n->u.s)) + n = closest((struct strmap *)map, member, memberlen); + if (!strncmp(member, n->u.s, memberlen) && !n->u.s[memberlen]) return n->v; } errno = ENOENT; return NULL; } +void *strmap_get_(const struct strmap *map, const char *member) +{ + return strmap_getn_(map, member, strlen(member)); +} + bool strmap_add_(struct strmap *map, const char *member, const void *value) { size_t len = strlen(member); @@ -68,7 +73,7 @@ bool strmap_add_(struct strmap *map, const char *member, const void *value) } /* Find closest existing member. */ - n = closest(map, member); + n = closest(map, member, len); /* Find where they differ. */ for (byte_num = 0; n->u.s[byte_num] == member[byte_num]; byte_num++) { diff --git a/ccan/strmap/strmap.h b/ccan/strmap/strmap.h index 0b32e6be..8724c31d 100644 --- a/ccan/strmap/strmap.h +++ b/ccan/strmap/strmap.h @@ -72,7 +72,7 @@ static inline bool strmap_empty_(const struct strmap *map) /** * strmap_get - get a value from a string map * @map: the typed strmap to search. - * @member: the string to search for. + * @member: the string to search for (nul terminated) * * Returns the value, or NULL if it isn't in the map (and sets errno = ENOENT). * @@ -85,6 +85,23 @@ static inline bool strmap_empty_(const struct strmap *map) tcon_cast((map), canary, strmap_get_(tcon_unwrap(map), (member))) void *strmap_get_(const struct strmap *map, const char *member); +/** + * strmap_getn - get a value from a string map + * @map: the typed strmap to search. + * @member: the string to search for. + * @memberlen: the length of @member. + * + * Returns the value, or NULL if it isn't in the map (and sets errno = ENOENT). + * + * Example: + * val = strmap_getn(&map, "hello", 5); + * if (val) + * printf("hello => %i\n", *val); + */ +#define strmap_getn(map, member, n) \ + tcon_cast((map), canary, strmap_getn_(tcon_unwrap(map), (member), (n))) +void *strmap_getn_(const struct strmap *map, const char *member, size_t n); + /** * strmap_add - place a member in the string map. * @map: the typed strmap to add to. diff --git a/ccan/tal/tal.c b/ccan/tal/tal.c index a4954111..1230d8ca 100644 --- a/ccan/tal/tal.c +++ b/ccan/tal/tal.c @@ -28,7 +28,8 @@ enum prop_type { struct tal_hdr { struct list_node list; - struct prop_hdr *prop; + /* Use is_prop_hdr tell if this is a struct prop_hdr or string! */ + char *prop; /* XOR with TAL_PTR_OBFUSTICATOR */ intptr_t parent_child; size_t bytelen; @@ -36,7 +37,8 @@ struct tal_hdr { struct prop_hdr { enum prop_type type; - struct prop_hdr *next; + /* Use is_prop_hdr to tell if this is a struct prop_hdr or string! */ + char *next; }; struct children { @@ -72,7 +74,7 @@ static struct { struct tal_hdr hdr; struct children c; } null_parent = { { { &null_parent.hdr.list, &null_parent.hdr.list }, - &null_parent.c.hdr, TAL_PTR_OBFUSTICATOR, 0 }, + (char *)&null_parent.c.hdr, TAL_PTR_OBFUSTICATOR, 0 }, { { CHILDREN, NULL }, &null_parent.hdr, { { &null_parent.c.children.n, @@ -123,9 +125,11 @@ void tal_cleanup(void) } /* We carefully start all real properties with a zero byte. */ -static bool is_literal(const struct prop_hdr *prop) +static struct prop_hdr *is_prop_hdr(const char *ptr) { - return ((char *)prop)[0] != 0; + if (*ptr != 0) + return NULL; + return (struct prop_hdr *)ptr; } #ifndef NDEBUG @@ -174,8 +178,11 @@ static struct tal_hdr *to_tal_hdr(const void *ctx) check_bounds(ignore_destroying_bit(t->parent_child)); check_bounds(t->list.next); check_bounds(t->list.prev); - if (t->prop && !is_literal(t->prop)) - check_bounds(t->prop); + if (t->prop) { + struct prop_hdr *p = is_prop_hdr(t->prop); + if (p) + check_bounds(p); + } return t; } @@ -215,13 +222,12 @@ static void notify(const struct tal_hdr *ctx, enum tal_notify_type type, const void *info, int saved_errno) { - const struct prop_hdr *p; + const char *ptr; + const struct prop_hdr *p; - for (p = ctx->prop; p; p = p->next) { + for (ptr = ctx->prop; ptr && (p = is_prop_hdr(ptr)) != NULL; ptr = p->next) { struct notifier *n; - if (is_literal(p)) - break; if (p->type != NOTIFIER) continue; n = (struct notifier *)p; @@ -255,29 +261,54 @@ static void *allocate(size_t size) return ret; } -static struct prop_hdr **find_property_ptr(const struct tal_hdr *t, - enum prop_type type) +/* Returns a pointer to the pointer: can cast (*ret) to a (struct prop_ptr *) */ +static char **find_property_ptr(struct tal_hdr *t, enum prop_type type) { - struct prop_hdr **p; + char **ptr; + struct prop_hdr *p; - for (p = (struct prop_hdr **)&t->prop; *p; p = &(*p)->next) { - if (is_literal(*p)) { - if (type == NAME) - return p; - break; - } - if ((*p)->type == type) - return p; - } - return NULL; + /* NAME is special, as it can be a literal: see find_name_property */ + assert(type != NAME); + for (ptr = &t->prop; *ptr; ptr = &p->next) { + if (!is_prop_hdr(*ptr)) + break; + p = (struct prop_hdr *)*ptr; + if (p->type == type) + return ptr; + } + return NULL; +} + +/* This is special: + * NULL - not found + * *literal: true - char **, pointer to literal pointer. + * *literal: false - struct prop_hdr **, pointer to header ptr. + */ +static char **find_name_property(struct tal_hdr *t, bool *literal) +{ + char **ptr; + struct prop_hdr *p; + + for (ptr = &t->prop; *ptr; ptr = &p->next) { + if (!is_prop_hdr(*ptr)) { + *literal = true; + return ptr; + } + p = (struct prop_hdr *)*ptr; + if (p->type == NAME) { + *literal = false; + return ptr; + } + } + return NULL; } -static void *find_property(const struct tal_hdr *parent, enum prop_type type) +static void *find_property(struct tal_hdr *parent, enum prop_type type) { - struct prop_hdr **p = find_property_ptr(parent, type); + char **ptr = find_property_ptr(parent, type); - if (p) - return *p; + if (ptr) + return (struct prop_hdr *)*ptr; return NULL; } @@ -287,7 +318,7 @@ static void init_property(struct prop_hdr *hdr, { hdr->type = type; hdr->next = parent->prop; - parent->prop = hdr; + parent->prop = (char *)hdr; } static struct notifier *add_notifier_property(struct tal_hdr *t, @@ -321,17 +352,20 @@ static enum tal_notify_type del_notifier_property(struct tal_hdr *t, bool match_extra_arg, void *extra_arg) { - struct prop_hdr **p; + char **ptr; + struct prop_hdr *p; - for (p = (struct prop_hdr **)&t->prop; *p; p = &(*p)->next) { + for (ptr = &t->prop; *ptr; ptr = &p->next) { struct notifier *n; enum tal_notify_type types; - if (is_literal(*p)) + p = is_prop_hdr(*ptr); + if (!p) break; - if ((*p)->type != NOTIFIER) + + if (p->type != NOTIFIER) continue; - n = (struct notifier *)*p; + n = (struct notifier *)p; if (n->u.notifyfn != fn) continue; @@ -341,8 +375,8 @@ static enum tal_notify_type del_notifier_property(struct tal_hdr *t, && extra_arg != EXTRA_ARG(n)) continue; - *p = (*p)->next; - freefn(n); + *ptr = p->next; + freefn(p); return types & ~(NOTIFY_IS_DESTRUCTOR|NOTIFY_EXTRA_ARG); } return 0; @@ -388,7 +422,8 @@ static bool add_child(struct tal_hdr *parent, struct tal_hdr *child) static void del_tree(struct tal_hdr *t, const tal_t *orig, int saved_errno) { - struct prop_hdr **prop, *p, *next; + struct prop_hdr *prop; + char *ptr, *next; assert(!taken(from_tal_hdr(t))); @@ -402,10 +437,10 @@ static void del_tree(struct tal_hdr *t, const tal_t *orig, int saved_errno) notify(t, TAL_NOTIFY_FREE, (tal_t *)orig, saved_errno); /* Now free children and groups. */ - prop = find_property_ptr(t, CHILDREN); + prop = find_property(t, CHILDREN); if (prop) { struct tal_hdr *i; - struct children *c = (struct children *)*prop; + struct children *c = (struct children *)prop; while ((i = list_top(&c->children, struct tal_hdr, list))) { list_del(&i->list); @@ -414,9 +449,9 @@ static void del_tree(struct tal_hdr *t, const tal_t *orig, int saved_errno) } /* Finally free our properties. */ - for (p = t->prop; p && !is_literal(p); p = next) { - next = p->next; - freefn(p); + for (ptr = t->prop; ptr && (prop = is_prop_hdr(ptr)); ptr = next) { + next = prop->next; + freefn(ptr); } freefn(t); } @@ -590,25 +625,34 @@ bool tal_del_destructor2_(const tal_t *ctx, void (*destroy)(void *me, void *arg) bool tal_set_name_(tal_t *ctx, const char *name, bool literal) { struct tal_hdr *t = debug_tal(to_tal_hdr(ctx)); - struct prop_hdr **prop = find_property_ptr(t, NAME); + bool was_literal; + char **nptr; /* Get rid of any old name */ - if (prop) { - struct name *name = (struct name *)*prop; - if (is_literal(&name->hdr)) - *prop = NULL; - else { - *prop = name->hdr.next; - freefn(name); - } + nptr = find_name_property(t, &was_literal); + if (nptr) { + if (was_literal) + *nptr = NULL; + else { + struct name *oldname; + + oldname = (struct name *)*nptr; + *nptr = oldname->hdr.next; + freefn(oldname); + } } if (literal && name[0]) { - struct prop_hdr **p; + char **ptr; + struct prop_hdr *prop; /* Append literal. */ - for (p = &t->prop; *p && !is_literal(*p); p = &(*p)->next); - *p = (struct prop_hdr *)name; + for (ptr = &t->prop; *ptr; ptr = &prop->next) { + prop = is_prop_hdr(*ptr); + if (!prop) + break; + } + *ptr = (char *)name; } else if (!add_name_property(t, name)) return false; @@ -620,15 +664,16 @@ bool tal_set_name_(tal_t *ctx, const char *name, bool literal) const char *tal_name(const tal_t *t) { - struct name *n; + char **nptr; + bool literal; - n = find_property(debug_tal(to_tal_hdr(t)), NAME); - if (!n) + nptr = find_name_property(debug_tal(to_tal_hdr(t)), &literal); + if (!nptr) return NULL; + if (literal) + return *nptr; - if (is_literal(&n->hdr)) - return (const char *)n; - return n->name; + return ((struct name *)(*nptr))->name; } size_t tal_bytelen(const tal_t *ptr) @@ -767,11 +812,17 @@ out: } void *tal_dup_(const tal_t *ctx, const void *p, size_t size, - size_t n, size_t extra, const char *label) + size_t n, size_t extra, bool nullok, const char *label) { void *ret; size_t nbytes = size; + if (nullok && p == NULL) { + /* take(NULL) works. */ + (void)taken(p); + return NULL; + } + if (!adjust_size(&nbytes, n)) { if (taken(p)) tal_free(p); @@ -797,11 +848,16 @@ void *tal_dup_(const tal_t *ctx, const void *p, size_t size, } ret = tal_alloc_arr_(ctx, size, n + extra, false, label); - if (ret) + if (ret && p) memcpy(ret, p, nbytes); return ret; } +void *tal_dup_talarr_(const tal_t *ctx, const tal_t *src TAKES, const char *label) +{ + return tal_dup_(ctx, src, 1, tal_bytelen(src), 0, true, label); +} + void tal_set_backend(void *(*alloc_fn)(size_t size), void *(*resize_fn)(void *, size_t size), void (*free_fn)(void *), @@ -821,39 +877,41 @@ void tal_set_backend(void *(*alloc_fn)(size_t size), static void dump_node(unsigned int indent, const struct tal_hdr *t) { unsigned int i; - const struct prop_hdr *p; + const struct prop_hdr *prop; + const char *ptr; for (i = 0; i < indent; i++) - printf(" "); - printf("%p len=%zu", t, t->bytelen); - for (p = t->prop; p; p = p->next) { + fprintf(stderr, " "); + fprintf(stderr, "%p len=%zu", t, t->bytelen); + for (ptr = t->prop; ptr; ptr = prop->next) { struct children *c; struct name *n; struct notifier *no; - if (is_literal(p)) { - printf(" \"%s\"", (const char *)p); + prop = is_prop_hdr(ptr); + if (!prop) { + fprintf(stderr, " \"%s\"", ptr); break; } - switch (p->type) { + switch (prop->type) { case CHILDREN: - c = (struct children *)p; - printf(" CHILDREN(%p):parent=%p,children={%p,%p}\n", - p, c->parent, + c = (struct children *)prop; + fprintf(stderr, " CHILDREN(%p):parent=%p,children={%p,%p}", + prop, c->parent, c->children.n.prev, c->children.n.next); break; case NAME: - n = (struct name *)p; - printf(" NAME(%p):%s", p, n->name); + n = (struct name *)prop; + fprintf(stderr, " NAME(%p):%s", prop, n->name); break; case NOTIFIER: - no = (struct notifier *)p; - printf(" NOTIFIER(%p):fn=%p", p, no->u.notifyfn); + no = (struct notifier *)prop; + fprintf(stderr, " NOTIFIER(%p):fn=%p", prop, no->u.notifyfn); break; default: - printf(" **UNKNOWN(%p):%i**", p, p->type); + fprintf(stderr, " **UNKNOWN(%p):%i**", prop, prop->type); } } - printf("\n"); + fprintf(stderr, "\n"); } static void tal_dump_(unsigned int level, const struct tal_hdr *t) @@ -862,7 +920,7 @@ static void tal_dump_(unsigned int level, const struct tal_hdr *t) dump_node(level, t); - children = find_property(t, CHILDREN); + children = find_property((struct tal_hdr *)t, CHILDREN); if (children) { struct tal_hdr *i; @@ -893,7 +951,8 @@ static bool check_err(struct tal_hdr *t, const char *errorstr, static bool check_node(struct children *parent_child, struct tal_hdr *t, const char *errorstr) { - struct prop_hdr *p; + struct prop_hdr *prop; + char *p; struct name *name = NULL; struct children *children = NULL; @@ -903,23 +962,24 @@ static bool check_node(struct children *parent_child, if (ignore_destroying_bit(t->parent_child) != parent_child) return check_err(t, errorstr, "incorrect parent"); - for (p = t->prop; p; p = p->next) { - if (is_literal(p)) { + for (p = t->prop; p; p = prop->next) { + prop = is_prop_hdr(p); + if (!prop) { if (name) return check_err(t, errorstr, "has extra literal"); break; } - if (!in_bounds(p)) + if (!in_bounds(prop)) return check_err(t, errorstr, "has bad property pointer"); - switch (p->type) { + switch (prop->type) { case CHILDREN: if (children) return check_err(t, errorstr, "has two child nodes"); - children = (struct children *)p; + children = (struct children *)prop; break; case NOTIFIER: break; @@ -927,7 +987,7 @@ static bool check_node(struct children *parent_child, if (name) return check_err(t, errorstr, "has two names"); - name = (struct name *)p; + name = (struct name *)prop; break; default: return check_err(t, errorstr, "has unknown property"); diff --git a/ccan/tal/tal.h b/ccan/tal/tal.h index 8b7ffca5..c486f9e8 100644 --- a/ccan/tal/tal.h +++ b/ccan/tal/tal.h @@ -131,10 +131,11 @@ void *tal_free(const tal_t *p); /** * tal_steal - change the parent of a tal-allocated pointer. * @ctx: The new parent. - * @ptr: The tal allocated object to move. + * @ptr: The tal allocated object to move, or NULL. * * This may need to perform an allocation, in which case it may fail; thus - * it can return NULL, otherwise returns @ptr. + * it can return NULL, otherwise returns @ptr. If @ptr is NULL, this function does + * nothing. */ #if HAVE_STATEMENT_EXPR /* Weird macro avoids gcc's 'warning: value computed is not used'. */ @@ -351,10 +352,21 @@ tal_t *tal_parent(const tal_t *ctx); * tal_dup - duplicate an object. * @ctx: The tal allocated object to be parent of the result (may be NULL). * @type: the type (should match type of @p!) - * @p: the object to copy (or reparented if take()) + * @p: the object to copy (or reparented if take()). Must not be NULL. */ #define tal_dup(ctx, type, p) \ - tal_dup_label(ctx, type, p, TAL_LABEL(type, "")) + tal_dup_label(ctx, type, p, TAL_LABEL(type, ""), false) + +/** + * tal_dup_or_null - duplicate an object, or just pass NULL. + * @ctx: The tal allocated object to be parent of the result (may be NULL). + * @type: the type (should match type of @p!) + * @p: the object to copy (or reparented if take()) + * + * if @p is NULL, just return NULL, otherwise to tal_dup(). + */ +#define tal_dup_or_null(ctx, type, p) \ + tal_dup_label(ctx, type, p, TAL_LABEL(type, ""), true) /** * tal_dup_arr - duplicate an array. @@ -368,7 +380,17 @@ tal_t *tal_parent(const tal_t *ctx); tal_dup_arr_label(ctx, type, p, n, extra, TAL_LABEL(type, "[]")) - +/** + * tal_dup_arr - duplicate a tal array. + * @ctx: The tal allocated object to be parent of the result (may be NULL). + * @type: the type (should match type of @p!) + * @p: the tal array to copy (or resized & reparented if take()) + * + * The comon case of duplicating an entire tal array. + */ +#define tal_dup_talarr(ctx, type, p) \ + ((type *)tal_dup_talarr_((ctx), tal_typechk_(p, type *), \ + TAL_LABEL(type, "[]"))) /* Lower-level interfaces, where you want to supply your own label string. */ #define tal_label(ctx, type, label) \ ((type *)tal_alloc_((ctx), sizeof(type), false, label)) @@ -378,13 +400,13 @@ tal_t *tal_parent(const tal_t *ctx); ((type *)tal_alloc_arr_((ctx), sizeof(type), (count), false, label)) #define tal_arrz_label(ctx, type, count, label) \ ((type *)tal_alloc_arr_((ctx), sizeof(type), (count), true, label)) -#define tal_dup_label(ctx, type, p, label) \ +#define tal_dup_label(ctx, type, p, label, nullok) \ ((type *)tal_dup_((ctx), tal_typechk_(p, type *), \ - sizeof(type), 1, 0, \ + sizeof(type), 1, 0, nullok, \ label)) #define tal_dup_arr_label(ctx, type, p, n, extra, label) \ ((type *)tal_dup_((ctx), tal_typechk_(p, type *), \ - sizeof(type), (n), (extra), \ + sizeof(type), (n), (extra), false, \ label)) /** @@ -455,7 +477,7 @@ bool tal_check(const tal_t *ctx, const char *errorstr); #ifdef CCAN_TAL_DEBUG /** - * tal_dump - dump entire tal tree. + * tal_dump - dump entire tal tree to stderr. * * This is a helper for debugging tal itself, which dumps all the tal internal * state. @@ -504,7 +526,9 @@ void *tal_alloc_arr_(const tal_t *ctx, size_t bytes, size_t count, bool clear, const char *label); void *tal_dup_(const tal_t *ctx, const void *p TAKES, size_t size, - size_t n, size_t extra, const char *label); + size_t n, size_t extra, bool nullok, const char *label); +void *tal_dup_talarr_(const tal_t *ctx, const tal_t *src TAKES, + const char *label); tal_t *tal_steal_(const tal_t *new_parent, const tal_t *t); diff --git a/ccan/tal/test/run-notifier.c b/ccan/tal/test/run-notifier.c index 150f00ad..47e43640 100644 --- a/ccan/tal/test/run-notifier.c +++ b/ccan/tal/test/run-notifier.c @@ -13,8 +13,8 @@ static void *my_realloc(void *old, size_t size) void *new = realloc(old, size); if (new == old) { void *p = malloc(size); - memcpy(p, old, size); - free(old); + memcpy(p, new, size); + free(new); new = p; } return new; diff --git a/ccan/tally/tally.c b/ccan/tally/tally.c index 29f05558..5cc3352a 100644 --- a/ccan/tally/tally.c +++ b/ccan/tally/tally.c @@ -56,7 +56,7 @@ static unsigned bucket_of(ssize_t min, unsigned step_bits, ssize_t val) return 0; } assert(step_bits < SIZET_BITS); - return (size_t)(val - min) >> step_bits; + return ((size_t)val - (size_t)min) >> step_bits; } /* Return the min value in bucket b. */ @@ -67,7 +67,7 @@ static ssize_t bucket_min(ssize_t min, unsigned step_bits, unsigned b) return min; } assert(step_bits < SIZET_BITS); - return min + ((ssize_t)b << step_bits); + return min + ((size_t)b << step_bits); } /* Does shifting by this many bits truncate the number? */ @@ -76,6 +76,9 @@ static bool shift_overflows(size_t num, unsigned bits) if (bits == 0) { return false; } + if (bits >= SIZET_BITS) { + return true; + } return ((num << bits) >> 1) != (num << (bits - 1)); } @@ -94,7 +97,7 @@ static void renormalize(struct tally *tally, /* If we don't have sufficient range, increase step bits until * buckets cover entire range of ssize_t anyway. */ - range = (new_max - new_min) + 1; + range = ((size_t)new_max - (size_t)new_min) + 1; while (!shift_overflows(tally->buckets, tally->step_bits) && range > ((size_t)tally->buckets << tally->step_bits)) { /* Collapse down. */ @@ -113,11 +116,13 @@ static void renormalize(struct tally *tally, memset(tally->counts, 0, sizeof(tally->counts[0]) * old_min); /* If we moved boundaries, adjust buckets to that ratio. */ - spill = (tally->min - new_min) % (1 << tally->step_bits); - for (i = 0; i < tally->buckets-1; i++) { - size_t adjust = (tally->counts[i] >> tally->step_bits) * spill; - tally->counts[i] -= adjust; - tally->counts[i+1] += adjust; + if (tally->step_bits < SIZET_BITS) { + spill = (tally->min - new_min) % ((size_t)1 << tally->step_bits); + for (i = 0; i < tally->buckets-1; i++) { + size_t adjust = (tally->counts[i] >> tally->step_bits) * spill; + tally->counts[i] -= adjust; + tally->counts[i+1] += adjust; + } } update: diff --git a/ccan/tap/tap.c b/ccan/tap/tap.c index bf8a276c..b3782104 100644 --- a/ccan/tap/tap.c +++ b/ccan/tap/tap.c @@ -43,6 +43,7 @@ static const char *todo_msg_fixed = "libtap malloc issue"; static int todo = 0; static int test_died = 0; static int test_pid; +void (*tap_fail_callback)(void) = NULL; /* Encapsulate the pthread code in a conditional. In the absence of libpthread the code does nothing. diff --git a/ccan/tap/tap.h b/ccan/tap/tap.h index 5b21ff75..22c245d5 100644 --- a/ccan/tap/tap.h +++ b/ccan/tap/tap.h @@ -246,6 +246,6 @@ void plan_skip_all(const char *reason); * * This can be used to ease debugging, or exit on the first failure. */ -void (*tap_fail_callback)(void); +extern void (*tap_fail_callback)(void); #endif /* CCAN_TAP_H */ diff --git a/ccan/tcon/tcon.h b/ccan/tcon/tcon.h index e0f84b38..df3aac88 100644 --- a/ccan/tcon/tcon.h +++ b/ccan/tcon/tcon.h @@ -147,8 +147,7 @@ * It evaluates to @x so you can chain it. */ #define tcon_check_ptr(x, canary, expr) \ - (sizeof(&(x)->_tcon[0].canary == (expr)) ? (x) : (x)) - + (sizeof((expr) ? (expr) : &(x)->_tcon[0].canary) ? (x) : (x)) /** * tcon_type - the type within a container (or void *) diff --git a/ccan/tcon/test/compile_fail-container1.c b/ccan/tcon/test/compile_fail-container1.c index 44645a7e..ed1d3e20 100644 --- a/ccan/tcon/test/compile_fail-container1.c +++ b/ccan/tcon/test/compile_fail-container1.c @@ -25,7 +25,7 @@ struct info_tcon { int main(void) { struct info_tcon info; - struct outer ovar; + struct outer ovar = { 0, { 0 } }; #ifdef FAIL #if !HAVE_TYPEOF #error We cannot detect type problems without HAVE_TYPEOF diff --git a/ccan/tcon/test/compile_fail-container1w.c b/ccan/tcon/test/compile_fail-container1w.c index 19ba5bdc..a03f6514 100644 --- a/ccan/tcon/test/compile_fail-container1w.c +++ b/ccan/tcon/test/compile_fail-container1w.c @@ -21,7 +21,7 @@ int main(void) { TCON_WRAP(struct info_base, TCON_CONTAINER(concan, struct outer, inner)) info; - struct outer ovar; + struct outer ovar = { 0, { 0 } }; #ifdef FAIL #if !HAVE_TYPEOF #error We cannot detect type problems without HAVE_TYPEOF diff --git a/ccan/tcon/test/compile_fail-container3.c b/ccan/tcon/test/compile_fail-container3.c index 9185225a..dfdfdba9 100644 --- a/ccan/tcon/test/compile_fail-container3.c +++ b/ccan/tcon/test/compile_fail-container3.c @@ -25,7 +25,7 @@ struct info_tcon { int main(void) { struct info_tcon info; - struct outer ovar; + struct outer ovar = { 0, { 0 } }; #ifdef FAIL #if !HAVE_TYPEOF #error We cannot detect type problems without HAVE_TYPEOF diff --git a/ccan/tcon/test/compile_fail-container3w.c b/ccan/tcon/test/compile_fail-container3w.c index 958e5c8b..a56e510f 100644 --- a/ccan/tcon/test/compile_fail-container3w.c +++ b/ccan/tcon/test/compile_fail-container3w.c @@ -21,7 +21,7 @@ int main(void) { TCON_WRAP(struct info_base, TCON_CONTAINER(concan, struct outer, inner)) info; - struct outer ovar; + struct outer ovar = { 0, { 0 } }; #ifdef FAIL #if !HAVE_TYPEOF #error We cannot detect type problems without HAVE_TYPEOF diff --git a/ccan/timer/timer.c b/ccan/timer/timer.c index 48bded19..ef6b2774 100644 --- a/ccan/timer/timer.c +++ b/ccan/timer/timer.c @@ -108,13 +108,10 @@ void timer_addrel(struct timers *timers, struct timer *t, struct timerel rel) t->time = time_to_grains(timemono_add(time_mono(), rel)); -#if TIME_HAVE_MONOTONIC - assert(t->time >= timers->base); -#else /* Added in the past? Treat it as imminent. */ if (t->time < timers->base) t->time = timers->base; -#endif + if (t->time < timers->first) timers->first = t->time; @@ -346,7 +343,11 @@ struct timer *timers_expire(struct timers *timers, struct timemono expire) unsigned int off; struct timer *t; - assert(now >= timers->base); + /* This can happen without TIME_HAVE_MONOTONIC, but I also have + * a report of OpenBSD 6.8 under virtualbox doing this. */ + if (now < timers->base) { + return NULL; + } if (!timers->level[0]) { if (list_empty(&timers->far)) diff --git a/ccan/ungraph/LICENSE b/ccan/ungraph/LICENSE new file mode 120000 index 00000000..2354d129 --- /dev/null +++ b/ccan/ungraph/LICENSE @@ -0,0 +1 @@ +../../licenses/BSD-MIT \ No newline at end of file diff --git a/ccan/ungraph/_info b/ccan/ungraph/_info new file mode 100644 index 00000000..6088c5e4 --- /dev/null +++ b/ccan/ungraph/_info @@ -0,0 +1,77 @@ +#include "config.h" +#include +#include + +/** + * ungraph - extract a graph from an ASCII diagram. + * + * This code takes an ASCII diagram and converts it to a graph. + * The following things are assumed: + * 1. The input consists of \n-terminated lines + * 2. /-\|+ are used for edges. + * 3. <^>v are used for arrowheads. + * 4. + can be used to cross-over. + * 5. No arrowheads or both-ended arrowheads are shortcuts for "both ways". + * 6. Edges can turn with or without a +, by up to 90 degrees. + * 7. Edges must go from one node name to another. + * 8. Any other text is an edge label which must be next to an edge or + * another label. + * + * License: BSD-MIT + * Example: + * // Convert an ASCII graph to Graphviz dot format + * #include + * #include + * #include + * + * // Just return the name as our node. + * static void *add_node(const tal_t *ctx, + * const char *name, + * const char **errstr, + * void *unused) + * { + * return (void *)name; + * } + * + * static const char *add_edge(const tal_t *ctx, + * void *source_node, + * void *dest_node, + * bool bidir, + * const char **labels, + * void *arg) + * { + * printf("%s -> %s;\n", + * (char *)source_node, (char *)dest_node); + * if (bidir) + * printf("%s -> %s;\n", + * (char *)dest_node, (char *)source_node); + * return NULL; + * } + * + * int main(int argc, char *argv[]) + * { + * const char *graph = grab_file(NULL, argv[1], NULL), *errmsg; + * printf("digraph %s {\n", argv[1] ? argv[1] : "stdin"); + * errmsg = ungraph(NULL, graph, add_node, add_edge, NULL); + * if (errmsg) + * errx(1, "%s", errmsg); + * printf("}"); + * } + * + * Author: Rusty Russell + */ +int main(int argc, char *argv[]) +{ + /* Expect exactly one argument */ + if (argc != 2) + return 1; + + if (strcmp(argv[1], "depends") == 0) { + printf("ccan/tal\n"); + printf("ccan/tal/str\n"); + printf("ccan/typesafe_cb\n"); + return 0; + } + + return 1; +} diff --git a/ccan/ungraph/test/run.c b/ccan/ungraph/test/run.c new file mode 100644 index 00000000..a9ae9be5 --- /dev/null +++ b/ccan/ungraph/test/run.c @@ -0,0 +1,140 @@ +#include +/* Include the C files directly. */ +#include +#include +#include + +static void *add_node(const tal_t *ctx, + const char *name, + const char **errstr, + char **out) +{ + tal_append_fmt(out, "add_node %s\n", (char *)name); + return (void *)tal_steal(ctx, name); +} + +static const char *add_edge(const tal_t *ctx, + void *source_node, + void *dest_node, + bool bidir, + const char **labels, + char **out) +{ + tal_append_fmt(out, "add_edge %s-%s bidir=%i\n", + (char *)source_node, + (char *)dest_node, + bidir); + for (size_t i = 0; i < tal_count(labels); i++) + tal_append_fmt(out, "- label %s\n", labels[i]); + return NULL; +} + +int main(void) +{ + const tal_t *ctx = tal(NULL, char); + char *out = tal_arrz(ctx, char, 1); + /* This is how many tests you plan to run */ + plan_tests(16); + + ok1(ungraph(ctx, + "AAA----->BBB", + add_node, add_edge, &out) == NULL); + ok1(strcmp(out, + "add_node AAA\n" + "add_node BBB\n" + "add_edge AAA-BBB bidir=0\n") == 0); + + out = tal_arrz(ctx, char, 1); + ok1(ungraph(ctx, + "AAA<------BBB", + add_node, add_edge, &out) == NULL); + ok1(strcmp(out, + "add_node AAA\n" + "add_node BBB\n" + "add_edge BBB-AAA bidir=0\n") == 0); + + out = tal_arrz(ctx, char, 1); + ok1(ungraph(ctx, + "AAA------BBB", + add_node, add_edge, &out) == NULL); + ok1(strcmp(out, + "add_node AAA\n" + "add_node BBB\n" + "add_edge AAA-BBB bidir=1\n") == 0); + + out = tal_arrz(ctx, char, 1); + ok1(ungraph(ctx, + "AAA<------>BBB", + add_node, add_edge, &out) == NULL); + ok1(strcmp(out, + "add_node AAA\n" + "add_node BBB\n" + "add_edge AAA-BBB bidir=1\n") == 0); + + out = tal_arrz(ctx, char, 1); + ok1(ungraph(ctx, + "AAA\n" + " ^ \n" + " | \n" + " | \n" + " v \n" + "BBB", + add_node, add_edge, &out) == NULL); + ok1(strcmp(out, + "add_node AAA\n" + "add_node BBB\n" + "add_edge AAA-BBB bidir=1\n") == 0); + + out = tal_arrz(ctx, char, 1); + ok1(ungraph(ctx, + "AAA\n" + " / \n" + "| \n" + " \\ \n" + " v \n" + " BBB\n", + add_node, add_edge, &out) == NULL); + ok1(strcmp(out, + "add_node AAA\n" + "add_node BBB\n" + "add_edge AAA-BBB bidir=0\n") == 0); + + out = tal_arrz(ctx, char, 1); + ok1(ungraph(ctx, + "AAA\n" + " / \n" + "|xyx \n" + " \\ \n" + " v \n" + " BBB", + add_node, add_edge, &out) == NULL); + ok1(strcmp(out, + "add_node AAA\n" + "add_node BBB\n" + "add_edge AAA-BBB bidir=0\n" + "- label xyx\n") == 0); + + out = tal_arrz(ctx, char, 1); + ok1(ungraph(ctx, + " AAA \n" + " | \n" + "A-+----B \n" + " | LABEL \n" + " | xyz\n" + " v \n" + " BBB", + add_node, add_edge, &out) == NULL); + ok1(strcmp(out, + "add_node AAA\n" + "add_node BBB\n" + "add_node A\n" + "add_node B\n" + "add_edge AAA-BBB bidir=0\n" + "add_edge A-B bidir=1\n" + "- label LABEL\n" + "- label xyz\n") == 0); + + tal_free(ctx); + /* This exits depending on whether all tests passed */ + return exit_status(); +} diff --git a/ccan/ungraph/ungraph.c b/ccan/ungraph/ungraph.c new file mode 100644 index 00000000..ba29d223 --- /dev/null +++ b/ccan/ungraph/ungraph.c @@ -0,0 +1,721 @@ +/* MIT (BSD) license - see LICENSE file for details */ +#include +#include + +struct xy { + size_t x, y; +}; + +struct text { + struct xy xy; + size_t width; + const char *text; + /* If it's a node, this is non-NULL */ + void *node; + /* NULL if none found, edge it one found, self if > 1 */ + struct edge *nearest_edge; +}; + +struct edge { + struct text *src, *dst; + bool bidir; + const char **labels; +}; + +/* This means we actually found two "nearest_edge" */ +static struct edge fake_edge; + +#define EDGES "+/-\\|" +#define ARROWS "<^>v" + +enum dir { + UP, + UP_RIGHT, + RIGHT, + DOWN_RIGHT, + DOWN, + DOWN_LEFT, + LEFT, + UP_LEFT, + INVALID, +}; + +static enum dir opposite_dir(enum dir dir) +{ + return (dir + 4) % 8; +} + +static enum dir clockwise(enum dir dir) +{ + return (dir + 1) % 8; +} + +static enum dir anticlockwise(enum dir dir) +{ + return (dir + 7) % 8; +} + +static enum dir dir_away(const struct text *t, struct xy xy) +{ + int xdir, ydir; + enum dir dirs[3][3] = {{UP_LEFT, UP, UP_RIGHT}, + {LEFT, INVALID, RIGHT}, + {DOWN_LEFT, DOWN, DOWN_RIGHT}}; + + if (xy.y < t->xy.y) + ydir = -1; + else if (xy.y > t->xy.y) + ydir = 1; + else + ydir = 0; + if (xy.x >= t->xy.x + t->width) + xdir = 1; + else if (xy.x < t->xy.x) + xdir = -1; + else + xdir = 0; + + return dirs[ydir+1][xdir+1]; +} + +static char line_for_dir(enum dir dir) +{ + switch (dir) { + case UP: + case DOWN: + return '|'; + case UP_RIGHT: + case DOWN_LEFT: + return '/'; + case RIGHT: + case LEFT: + return '-'; + case DOWN_RIGHT: + case UP_LEFT: + return '\\'; + case INVALID: + break; + } + abort(); +} + +static char arrow_for_dir(enum dir dir) +{ + switch (dir) { + case UP: + case UP_RIGHT: + case UP_LEFT: + return '^'; + case DOWN: + case DOWN_RIGHT: + case DOWN_LEFT: + return 'v'; + case LEFT: + return '<'; + case RIGHT: + return '>'; + case INVALID: + break; + } + abort(); +} + +static struct xy move_in_dir(struct xy xy, enum dir dir) +{ + switch (dir) { + case UP: + xy.y = xy.y - 1; + return xy; + case DOWN: + xy.y = xy.y + 1; + return xy; + case UP_RIGHT: + xy.x = xy.x + 1; + xy.y = xy.y - 1; + return xy; + case DOWN_LEFT: + xy.x = xy.x - 1; + xy.y = xy.y + 1; + return xy; + case RIGHT: + xy.x = xy.x + 1; + return xy; + case LEFT: + xy.x = xy.x - 1; + return xy; + case DOWN_RIGHT: + xy.x = xy.x + 1; + xy.y = xy.y + 1; + return xy; + case UP_LEFT: + xy.x = xy.x - 1; + xy.y = xy.y - 1; + return xy; + case INVALID: + break; + } + abort(); +} + +static char *sqc(char **sq, struct xy xy) +{ + return &sq[xy.y][xy.x]; +} + +/* Try straight ahead first, then a bit to either side, then + * finally left and right */ +static struct xy scan_move_next(struct xy xy, enum dir start, enum dir *cur) +{ + if (*cur == start) + *cur = clockwise(start); + else if (*cur == clockwise(start)) + *cur = anticlockwise(start); + else if (*cur == anticlockwise(start)) + *cur = anticlockwise(anticlockwise(start)); + else if (*cur == anticlockwise(anticlockwise(start))) + *cur = clockwise(clockwise(start)); + else { + *cur = INVALID; + return xy; + } + return move_in_dir(xy, *cur); +} + +static void start_perimeter(struct xy *xyp, enum dir *dirp, struct xy xy) +{ + *dirp = RIGHT; + xyp->x = xy.x - 1; + xyp->y = xy.y - 1; +} + +static void next_perimeter(struct xy *xyp, enum dir *dirp, struct xy xy, size_t width) +{ + *xyp = move_in_dir(*xyp, *dirp); + if (*dirp == RIGHT && xyp->x == xy.x + width) + *dirp = DOWN; + else if (*dirp == DOWN && xyp->y == xy.y + 1) + *dirp = LEFT; + else if (*dirp == LEFT && xyp->x == xy.x - 1) + *dirp = UP; + else if (*dirp == UP && xyp->y == xy.y - 2) + *dirp = INVALID; +} + +/* Useful iterators. */ +#define for_each_scan_dir(xyp, dirp, xy, dir) \ + for (*dirp = dir, *xyp = move_in_dir(xy, *dirp); \ + *dirp != INVALID; \ + *xyp = scan_move_next(xy, dir, dirp)) + +#define for_each_perimeter(xyp, dirp, xy, width) \ + for (start_perimeter(xyp, dirp, xy); \ + *dirp != INVALID; \ + next_perimeter(xyp, dirp, xy, width)) + +/* Canonicalizes str into array of characters, finds text strings. */ +static char **square(const tal_t *ctx, + const char *str, + struct text **texts) +{ + size_t width = 0, height = 0; + size_t line_len = 0; + char **sq; + struct text *cur_text; + size_t strlen; + + *texts = tal_arr(ctx, struct text, 0); + + strlen = 0; + for (size_t i = 0; str[i]; i++) { + if (str[i] == '\n') { + height++; + line_len = 0; + } else { + line_len++; + if (line_len > width) + width = line_len; + } + strlen++; + } + + /* If didn't end in \n, it's implied */ + if (line_len != 0) { + height++; + strlen++; + } + + /* For analysis simplicity, create a blank border. */ + sq = tal_arr(ctx, char *, height + 2); + for (size_t i = 0; i < height + 2; i++) { + sq[i] = tal_arr(sq, char, width + 2); + memset(sq[i], ' ', width + 2); + } + + /* Copy across and find text */ + cur_text = NULL; + width = height = 1; + for (size_t i = 0; i < strlen; i++) { + bool end_text; + bool eol; + + eol = (str[i] == '\n' || str[i] == '\0'); + if (!eol) + sq[height][width] = str[i]; + + /* v by itself handled separately below */ + if (strchr(EDGES ARROWS "\n", str[i]) && str[i] != 'v') { + end_text = (cur_text != NULL); + } else if (cur_text) { + /* Two spaces ends text */ + end_text = (str[i] == ' ' && str[i-1] == ' ') || eol; + } else if (str[i] != ' ') { + size_t num_texts = tal_count(*texts); + tal_resize(texts, num_texts+1); + cur_text = &(*texts)[num_texts]; + cur_text->xy.x = width; + cur_text->xy.y = height; + cur_text->width = 0; + cur_text->node = NULL; + cur_text->nearest_edge = NULL; + end_text = false; + } else + end_text = false; + + if (end_text) { + /* Trim final space */ + if (sq[cur_text->xy.y][cur_text->xy.x + cur_text->width-1] == ' ') + cur_text->width--; + /* Ignore lone 'v' */ + if (cur_text->width == 1 && sq[cur_text->xy.y][cur_text->xy.x] == 'v') + tal_resize(texts, tal_count(*texts)-1); + else { + cur_text->text = tal_strndup(ctx, &sq[cur_text->xy.y][cur_text->xy.x], + cur_text->width); + } + cur_text = NULL; + } + + if (cur_text) + cur_text->width++; + if (eol) { + height++; + width = 1; + } else + width++; + } + + return sq; +} + +/* If text was not previously a node, it is now! */ +static const char *text_now_node(const tal_t *ctx, + char **sq, + struct text *text, + void *(*add_node)(const tal_t *ctx, + const char *name, + const char **errstr, + void *arg), + void *arg) +{ + const char *err; + + /* Already a node? */ + if (text->node) + return NULL; + + text->node = add_node(ctx, text->text, &err, arg); + if (!text->node) + return err; + return NULL; +} + +static bool correct_line_char(char c, enum dir dir, enum dir *newdir) +{ + if (c == line_for_dir(dir)) { + *newdir = dir; + return true; + } else if (c == line_for_dir(anticlockwise(dir))) { + *newdir = anticlockwise(dir); + return true; + } else if (c == line_for_dir(clockwise(dir))) { + *newdir = clockwise(dir); + return true; + } + return false; +} + +static bool seek_line(char **sq, struct xy *xy, enum dir *dir) +{ + struct xy scan; + enum dir scandir; + + for_each_scan_dir(&scan, &scandir, *xy, *dir) { + if (correct_line_char(*sqc(sq, scan), scandir, &scandir)) + goto found; + /* + in front always works */ + if (*dir == scandir && *sqc(sq, scan) == '+') + goto found; + } + return false; + +found: + *xy = scan; + *dir = scandir; + return true; +} + +static bool seek_arrowhead(char **sq, struct xy *xy, enum dir *dir) +{ + struct xy scan; + enum dir scandir; + + for_each_scan_dir(&scan, &scandir, *xy, *dir) { + if (strchr(ARROWS, *sqc(sq, scan))) { + *xy = scan; + *dir = scandir; + return true; + } + } + return false; +} + +static struct text *in_text(struct text *texts, struct xy xy) +{ + for (size_t i = 0; i < tal_count(texts); i++) { + if (texts[i].xy.y != xy.y) + continue; + if (xy.x >= texts[i].xy.x + texts[i].width) + continue; + if (xy.x < texts[i].xy.x) + continue; + return texts + i; + } + return NULL; +} + +static struct text *seek_text(struct text *texts, + struct xy xy, enum dir dir) +{ + struct xy scan; + enum dir scandir; + + for_each_scan_dir(&scan, &scandir, xy, dir) { + struct text *t = in_text(texts, scan); + if (t) + return t; + } + return NULL; +} + +static void erase_line(char **sq, + struct xy xy, + enum dir dir, + enum dir prev_dir) +{ + char c = ' '; + + /* If we go straight through a +, convert for crossover */ + if (prev_dir == dir && *sqc(sq, xy) == '+') { + if (dir == UP || dir == DOWN) + c = '-'; + if (dir == LEFT || dir == RIGHT) + c = '|'; + } + *sqc(sq, xy) = c; +} + +static bool in_nearby(struct text **nearby, const struct text *t) +{ + for (size_t i = 0; i < tal_count(nearby); i++) { + if (nearby[i] == t) + return true; + } + return false; +} + +static void add_nearby(struct text ***nearby, + struct text *texts, + struct xy xy) +{ + struct xy perim; + enum dir pdir; + size_t n = tal_count(*nearby); + + for_each_perimeter(&perim, &pdir, xy, 1) { + struct text *t = in_text(texts, perim); + if (!t) + continue; + /* Don't care if it's already a node */ + if (t->node) + continue; + if (in_nearby(*nearby, t)) + continue; + tal_resize(nearby, n+1); + (*nearby)[n++] = t; + } +} + +/* Clears lines as it goes. */ +static struct text *follow_line(char **sq, + struct text *texts, + struct xy xy, + enum dir dir, + bool *arrow_src, + bool *arrow_dst, + bool *dangling, + struct text ***nearby) +{ + char expect_arrow = arrow_for_dir(opposite_dir(dir)); + enum dir prev_dir; + + *nearby = tal_arr(sq, struct text *, 0); + + if (*sqc(sq, xy) == expect_arrow) { + *arrow_src = true; + } else if (*sqc(sq, xy) == line_for_dir(dir)) { + *arrow_src = false; + } else if (*sqc(sq, xy) == line_for_dir(anticlockwise(dir))) { + *arrow_src = false; + dir = anticlockwise(dir); + } else if (*sqc(sq, xy) == line_for_dir(clockwise(dir))) { + *arrow_src = false; + dir = clockwise(dir); + } else { + *dangling = false; + /* No arrow is fine. */ + return NULL; + } + + erase_line(sq, xy, dir, INVALID); + add_nearby(nearby, texts, xy); + + *arrow_dst = false; + prev_dir = dir; + for (;;) { + /* Try to continue line */ + if (!*arrow_dst && seek_line(sq, &xy, &dir)) { + erase_line(sq, xy, dir, prev_dir); + add_nearby(nearby, texts, xy); + prev_dir = dir; + continue; + } + /* Look for arrow */ + if (!*arrow_dst && seek_arrowhead(sq, &xy, &dir)) { + erase_line(sq, xy, dir, prev_dir); + add_nearby(nearby, texts, xy); + *arrow_dst = true; + prev_dir = dir; + continue; + } + break; + } + + /* Must be in text! */ + *dangling = true; + return seek_text(texts, xy, dir); +} + +static const char *try_create_edge(const tal_t *ctx, + char **sq, + struct text *texts, + struct xy xy, + struct text *src, + void *(*add_node)(const tal_t *ctx, + const char *name, + const char **errstr, + void *arg), + void *arg, + struct edge **edge) +{ + struct text *dst; + bool arrow_src, arrow_dst, dangling; + struct text **nearby; + const char *err; + + *edge = NULL; + if (in_text(texts, xy)) + return NULL; + + dst = follow_line(sq, texts, xy, dir_away(src, xy), &arrow_src, &arrow_dst, &dangling, &nearby); + if (!dst) { + if (dangling) + return tal_fmt(ctx, "Found dangling arrow at (%zu,%zu)", xy.x-1, xy.y-1); + return NULL; + } + + /* If you weren't a node before, you are now! */ + err = text_now_node(ctx, sq, src, add_node, arg); + if (err) + return err; + err = text_now_node(ctx, sq, dst, add_node, arg); + if (err) + return err; + + /* No arrows equiv to both arrows */ + if (!arrow_src && !arrow_dst) + arrow_src = arrow_dst = true; + + *edge = tal(NULL, struct edge); + if (arrow_dst) { + (*edge)->src = src; + (*edge)->dst = dst; + (*edge)->bidir = arrow_src; + } else { + (*edge)->src = dst; + (*edge)->dst = src; + (*edge)->bidir = false; + } + (*edge)->labels = tal_arr(*edge, const char *, 0); + + /* Now record any texts it passed by, in case they're labels */ + for (size_t i = 0; i < tal_count(nearby); i++) { + /* We might have just made it a node */ + if (nearby[i]->node) + continue; + /* Already has an edge? Mark it as near two, to error + * later if it's a label */ + if (nearby[i]->nearest_edge) + nearby[i]->nearest_edge = &fake_edge; + else + nearby[i]->nearest_edge = *edge; + } + + return NULL; +} + +static const char *scan_for_unused(const tal_t *ctx, + struct text *texts, + char **sq) +{ + struct xy xy; + for (xy.y = 0; xy.y < tal_count(sq); xy.y++) { + for (xy.x = 0; xy.x < tal_count(sq[xy.y]); xy.x++) { + if (in_text(texts, xy)) + continue; + if (*sqc(sq,xy) != ' ') + return tal_fmt(ctx, "Unused '%c' at (%zu,%zu)", + *sqc(sq, xy), xy.x-1, xy.y-1); + } + } + return NULL; +} + +static void add_label(struct edge *edge, const struct text *label) +{ + size_t n = tal_count(edge->labels); + tal_resize(&edge->labels, n+1); + edge->labels[n] = label->text; +} + +const char *ungraph_(const tal_t *ctx, + const char *str, + void *(*add_node)(const tal_t *ctx, + const char *name, + const char **errstr, + void *arg), + const char *(*add_edge)(const tal_t *ctx, + void *source_node, + void *dest_node, + bool bidir, + const char **labels, + void *arg), + void *arg) +{ + /* To hold all our temporaries! */ + const tal_t *sub = tal(ctx, char); + char **sq; + struct text *texts, *remaining_label; + const char *err; + bool progress; + struct edge **edges = tal_arr(sub, struct edge *, 0); + size_t num_edges = 0; + + /* We create canonical square, find texts. */ + sq = square(sub, str, &texts); + + /* Now search for arrows around each text, cleaning + * as we go! */ + for (size_t i = 0; i < tal_count(texts); i++) { + struct xy perim; + enum dir pdir; + struct text *t = &texts[i]; + + for_each_perimeter(&perim, &pdir, t->xy, t->width) { + struct edge *edge; + err = try_create_edge(ctx, sq, texts, perim, t, add_node, arg, &edge); + if (err) + goto fail; + if (edge) { + tal_resize(&edges, num_edges+1); + edges[num_edges++] = tal_steal(edges, edge); + } + } + } + + /* Now attach any remaining labels */ + for (size_t i = 0; i < tal_count(texts); i++) { + struct text *t = &texts[i]; + + if (t->node) + continue; + if (t->nearest_edge == &fake_edge) { + err = tal_fmt(ctx, "Label at (%zu,%zu) near more than one edge", + t->xy.x-1, t->xy.y-1); + goto fail; + } + if (t->nearest_edge) + add_label(t->nearest_edge, t); + } + + /* Any remaining labels must be attached to already-attached labels */ + do { + progress = false; + remaining_label = NULL; + + for (size_t i = 0; i < tal_count(texts); i++) { + struct xy perim; + enum dir pdir; + struct text *t = &texts[i]; + + if (t->node || t->nearest_edge) + continue; + + remaining_label = t; + for_each_perimeter(&perim, &pdir, t->xy, t->width) { + struct text *neighbor = in_text(texts, perim); + if (!neighbor || neighbor->node || !neighbor->nearest_edge) + continue; + t->nearest_edge = neighbor->nearest_edge; + add_label(t->nearest_edge, t); + progress = true; + break; + } + } + } while (progress); + + if (remaining_label) { + err = tal_fmt(ctx, "Label at (%zu,%zu) not near any edge", + remaining_label->xy.x-1, + remaining_label->xy.y-1); + goto fail; + } + + err = scan_for_unused(ctx, texts, sq); + if (err) + goto fail; + + /* Now add edges, complete with labels */ + for (size_t i = 0; i < tal_count(edges); i++) { + err = add_edge(ctx, edges[i]->src->node, edges[i]->dst->node, + edges[i]->bidir, edges[i]->labels, arg); + if (err) + goto fail; + } + + tal_free(sub); + return NULL; + +fail: + tal_free(sub); + return err; +} diff --git a/ccan/ungraph/ungraph.h b/ccan/ungraph/ungraph.h new file mode 100644 index 00000000..8480b359 --- /dev/null +++ b/ccan/ungraph/ungraph.h @@ -0,0 +1,53 @@ +/* MIT (BSD) license - see LICENSE file for details */ +#ifndef CCAN_UNGRAPH_H +#define CCAN_UNGRAPH_H +#include +#include + +/** + * ungraph: extract a graph from an ASCII graph. + * @ctx: context for callbacks, and/or returned errstr. + * @str: a string containing a graph. + * @add_node: callback for a new node, returns node. + * @add_edge: callback for a new edge, with tal_count(labels). + * @arg: callback argument. + * + * On success, returns NULL. On failure, returns some error message + * (allocated off @ctx, or returned from callbacks). + * + * If @add_node returns NULL, it must set @errstr. @add_edge + * returns the error message directly. + * + * @add_node and @add_edge can tal_steal the name/labels if they want, + * otherwise they will be freed. + */ +const char *ungraph_(const tal_t *ctx, + const char *str, + void *(*add_node)(const tal_t *ctx, + const char *name, + const char **errstr, + void *arg), + const char *(*add_edge)(const tal_t *ctx, + void *source_node, + void *dest_node, + bool bidir, + const char **labels, + void *arg), + void *arg); + +#define ungraph(ctx, str, add_node, add_edge, arg) \ + ungraph_((ctx), (str), \ + typesafe_cb_preargs(void *, void *, \ + (add_node), (arg), \ + const tal_t *, \ + const char *, \ + const char **errstr), \ + typesafe_cb_preargs(const char *, void *, \ + (add_edge), (arg), \ + const tal_t *, \ + void *, \ + void *, \ + bool, \ + const char **), \ + arg) +#endif /* CCAN_UNGRAPH_H */ diff --git a/ccan/utf8/test/run-decode.c b/ccan/utf8/test/run-decode.c index 34ecb1d1..7b991777 100644 --- a/ccan/utf8/test/run-decode.c +++ b/ccan/utf8/test/run-decode.c @@ -117,7 +117,7 @@ test_unicode_scalar_value(void) { char src[4]; /* Unicode scalar value [U+0000, U+007F] */ - for (ord = 0x0000; ord <= 0x007F; ord++) { + for (ord = 0x0001; ord <= 0x007F; ord++) { encode_ord(ord, 1, src); TEST_UTF8(src, 1, ord ? 0 : ERANGE); } @@ -255,7 +255,7 @@ test_continuations(void) { int main(int argc, char **argv) { - plan_tests(2190906); + plan_tests(2190906 - 1); test_unicode_scalar_value(); test_surrogates(); test_non_shortest_form(); diff --git a/ccan/utf8/utf8.c b/ccan/utf8/utf8.c index 346d2d95..cb18041a 100644 --- a/ccan/utf8/utf8.c +++ b/ccan/utf8/utf8.c @@ -63,6 +63,8 @@ bool utf8_decode(struct utf8_state *utf8_state, char c) /* First character in sequence. */ if (((unsigned char)c & 0x80) == 0) { /* ASCII, easy. */ + if (c == 0) + goto bad_encoding; utf8_state->total_len = 1; utf8_state->c = c; goto finished_decoding; diff --git a/ccan/utf8/utf8.h b/ccan/utf8/utf8.h index a095f02e..9a746968 100644 --- a/ccan/utf8/utf8.h +++ b/ccan/utf8/utf8.h @@ -33,7 +33,7 @@ static inline void utf8_state_init(struct utf8_state *utf8_state) * Otherwise returns true, @utf8_state can be reused without initializeation, * and sets errno: * 0: success - * EINVAL: bad encoding. + * EINVAL: bad encoding (including a NUL character). * EFBIG: not a minimal encoding. * ERANGE: encoding of invalid character. * diff --git a/ccan/version/version.h b/ccan/version/version.h index 8820f174..9e805a3a 100644 --- a/ccan/version/version.h +++ b/ccan/version/version.h @@ -55,7 +55,7 @@ static inline uint16_t version_minor(const struct version v) { */ static inline struct version version(uint16_t major, uint16_t minor) { - struct version v = { ._v = major << 16 | minor }; + struct version v = { ._v = (uint32_t)major << 16 | minor }; return v; } diff --git a/tools/ccanlint/async.c b/tools/ccanlint/async.c index f3d1a5ba..3f88bbcd 100644 --- a/tools/ccanlint/async.c +++ b/tools/ccanlint/async.c @@ -151,11 +151,12 @@ static void reap_output(void) int old_len, len; /* This length includes nul terminator! */ old_len = tal_count(c->output); - tal_resize(&c->output, old_len + 1024); - len = read(c->output_fd, c->output + old_len - 1, 1024); + tal_resize(&c->output, old_len + 65536); + len = read(c->output_fd, c->output + old_len - 1, 65536); if (len < 0) err(1, "Reading from async command"); - tal_resize(&c->output, old_len + len); + if (len != 65536) + tal_resize(&c->output, old_len + len); c->output[old_len + len - 1] = '\0'; if (len == 0) { struct rusage ru; diff --git a/tools/ccanlint/licenses.h b/tools/ccanlint/licenses.h index 60d20376..b921fa62 100644 --- a/tools/ccanlint/licenses.h +++ b/tools/ccanlint/licenses.h @@ -30,7 +30,7 @@ struct license_info { }; /* Is [project license][file license] compatible? */ -bool license_compatible[LICENSE_UNKNOWN+1][LICENSE_UNKNOWN]; +extern bool license_compatible[LICENSE_UNKNOWN+1][LICENSE_UNKNOWN]; extern const struct license_info licenses[]; diff --git a/tools/configurator/configurator.c b/tools/configurator/configurator.c index c8b131cc..7d8f6b09 100644 --- a/tools/configurator/configurator.c +++ b/tools/configurator/configurator.c @@ -142,6 +142,9 @@ static const struct test base_tests[] = { { "HAVE_ATTRIBUTE_NONNULL", "__attribute__((nonnull)) support", "DEFINES_FUNC", NULL, NULL, "static char *__attribute__((nonnull)) func(char *p) { return p; }" }, + { "HAVE_ATTRIBUTE_RETURNS_NONNULL", "__attribute__((returns_nonnull)) support", + "DEFINES_FUNC", NULL, NULL, + "static const char *__attribute__((returns_nonnull)) func(void) { return \"hi\"; }" }, { "HAVE_ATTRIBUTE_SENTINEL", "__attribute__((sentinel)) support", "DEFINES_FUNC", NULL, NULL, "static int __attribute__((sentinel)) func(int i, ...) { return i; }" }, @@ -194,7 +197,7 @@ static const struct test base_tests[] = { "return __builtin_clzll(1) == (sizeof(long long)*8 - 1) ? 0 : 1;" }, { "HAVE_BUILTIN_CTZ", "__builtin_ctz support", "INSIDE_MAIN", NULL, NULL, - "return __builtin_ctz(1 << (sizeof(int)*8 - 1)) == (sizeof(int)*8 - 1) ? 0 : 1;" }, + "return __builtin_ctz(1U << (sizeof(int)*8 - 1)) == (sizeof(int)*8 - 1) ? 0 : 1;" }, { "HAVE_BUILTIN_CTZL", "__builtin_ctzl support", "INSIDE_MAIN", NULL, NULL, "return __builtin_ctzl(1UL << (sizeof(long)*8 - 1)) == (sizeof(long)*8 - 1) ? 0 : 1;" }, @@ -411,7 +414,7 @@ static const struct test base_tests[] = { "int main(int argc, char *argv[]) {\n" " (void)argc;\n" " char pad[sizeof(int *) * 1];\n" - " strncpy(pad, argv[0], sizeof(pad));\n" + " memcpy(pad, argv[0], sizeof(pad));\n" " int *x = (int *)pad, *y = (int *)(pad + 1);\n" " return *x == *y;\n" "}\n" }, @@ -468,7 +471,7 @@ static const struct test base_tests[] = { "#include \n" "#include \n" "static int worked = 0;\n" - "static char stack[1024];\n" + "static char stack[8192];\n" "static ucontext_t a, b;\n" "static void fn(void *p, void *q) {\n" " void *cp = &worked;\n" @@ -495,6 +498,43 @@ static const struct test base_tests[] = { " return __builtin_cpu_supports(\"mmx\");\n" "}" }, + { "HAVE_CLOSEFROM", "closefrom() offered by system", + "DEFINES_EVERYTHING", NULL, NULL, + "#include \n" + "#include \n" + "int main(void) {\n" + " closefrom(STDERR_FILENO + 1);\n" + " return 0;\n" + "}\n" + }, + { "HAVE_F_CLOSEM", "F_CLOSEM defined for fctnl.", + "DEFINES_EVERYTHING", NULL, NULL, + "#include \n" + "#include \n" + "int main(void) {\n" + " int res = fcntl(STDERR_FILENO + 1, F_CLOSEM, 0);\n" + " return res < 0;\n" + "}\n" + }, + { "HAVE_NR_CLOSE_RANGE", "close_range syscall available as __NR_close_range.", + "DEFINES_EVERYTHING", NULL, NULL, + "#include \n" + "#include \n" + "#include \n" + "int main(void) {\n" + " int res = syscall(__NR_close_range, STDERR_FILENO + 1, INT_MAX, 0);\n" + " return res < 0;\n" + "}\n" + }, + { "HAVE_F_MAXFD", "F_MAXFD defined for fcntl.", + "DEFINES_EVERYTHING", NULL, NULL, + "#include \n" + "#include \n" + "int main(void) {\n" + " int res = fcntl(0, F_MAXFD);\n" + " return res < 0;\n" + "}\n" + }, }; static void c12r_err(int eval, const char *fmt, ...) @@ -757,14 +797,12 @@ static bool run_test(const char *cmd, const char *wrapper, struct test *test) /* We run INSIDE_MAIN tests for sanity checking. */ if (strstr(test->style, "EXECUTE") || strstr(test->style, "INSIDE_MAIN")) { - char *cmd = malloc(strlen(wrapper) + strlen(" ." DIR_SEP OUTPUT_FILE) + 1); + char *runcmd = malloc(strlen(wrapper) + strlen(" ." DIR_SEP OUTPUT_FILE) + 1); - strcpy(cmd, wrapper); - strcat(cmd, " ." DIR_SEP OUTPUT_FILE); - output = run(cmd, &status); - if (wrapper) { - free(cmd); - } + strcpy(runcmd, wrapper); + strcat(runcmd, " ." DIR_SEP OUTPUT_FILE); + output = run(runcmd, &status); + free(runcmd); if (!strstr(test->style, "EXECUTE") && status != 0) c12r_errx(EXIT_BAD_TEST, "Test for %s failed with %i:\n%s", diff --git a/tools/read_config_header.c b/tools/read_config_header.c index da9ed0a3..da3e00cd 100644 --- a/tools/read_config_header.c +++ b/tools/read_config_header.c @@ -96,10 +96,11 @@ char *read_config_header(const char *ccan_dir, bool verbose) char *config_header; config_header = grab_file(NULL, fname); - tal_free(fname); - if (!config_header) + if (!config_header) { + tal_free(fname); return NULL; + } lines = tal_strsplit(config_header, config_header, "\n", STR_EMPTY_OK); for (i = 0; i < tal_count(lines) - 1; i++) { @@ -129,5 +130,6 @@ char *read_config_header(const char *ccan_dir, bool verbose) fname, cflags); } } + tal_free(fname); return config_header; } diff --git a/web/staticall.php b/web/staticall.php index 3e9d445a..c82a8bdb 100644 --- a/web/staticall.php +++ b/web/staticall.php @@ -4,7 +4,7 @@ include('logo.html'); include('menulist.html'); include('static-configuration'); -$tarballsize=round((filesize($argv[3]."/ccan.tar.bz2") + 1023) / 1024); +$tarballsize=round((filesize($argv[3]."/".$argv[4]) + 1023) / 1024); ?>

List of all CCAN modules:

@@ -16,7 +16,7 @@ download.

-Or you can just download the tarball of everything including CCAN tools (K). +Or you can just download the tarball of everything including CCAN tools (K).

@@ -25,7 +25,7 @@ Or you can just download the tarball of everything includ Get The Code

You can get each module as a tarball (see -list), get a tarball of the whole repository with tools, +list), get a tarball of the whole repository with tools, or clone our git repository (git clone git://git.ozlabs.org/~ccan/ccan) or the one on github.

Download