Files
puzzles/fuzzpuzz.c
Franklin Wei f37913002a Refine drawing API semantics to pass drawing * instead of void *
This changes the drawing API so that implementations receive a
`drawing *` pointer with each call, instead of a `void *` pointer as
they did previously. The `void *` context pointer has been moved to be
a member of the `drawing` structure (which has been made public), from
which it can be retrieved via the new `GET_HANDLE_AS_TYPE()` macro. To
signal this breaking change to downstream front end authors, I've
added a version number to the `drawing_api` struct, which will
hopefully force them to notice.

The motivation for this change is the upcoming introduction of a
draw_polygon_fallback() function, which will use a series of calls to
draw_line() to perform software polygon rasterization on platforms
without a native polygon fill primitive. This function is fairly
large, so I desired that it not be included in the binary
distribution, except on platforms which require it (e.g. my Rockbox
port). One way to achieve this is via link-time optimization (LTO,
a.k.a. "interprocedural optimization"/IPO), so that the code is
unconditionally compiled (preventing bit-rot) but only included in the
linked executable if it is actually referenced from elsewhere.
Practically, this precludes the otherwise straightforward route of
including a run-time check of the `draw_polygon` pointer in the
drawing.c middleware. Instead, Simon recommended that a front end be
able to set its `draw_polygon` field to point to
draw_polygon_fallback(). However, the old drawing API's semantics of
passing a `void *` pointer prevented this from working in practice,
since draw_polygon_fallback(), implemented in middleware, would not be
able to perform any drawing operations without a `drawing *` pointer;
with the new API, this restriction is removed, clearing the way for
that function's introduction.

This is a breaking change for front ends, which must update their
implementations of the drawing API to conform. The migration process
is fairly straightforward: every drawing API function which previously
took a `void *` context pointer should be updated to take a `drawing *`
pointer in its place. Then, where each such function would have
previously casted the `void *` pointer to a meaningful type, they now
instead retrieve the context pointer from the `handle` field of the
`drawing` structure. To make this transition easier, the
`GET_HANDLE_AS_TYPE()` macro is introduced to wrap the context pointer
retrieval (see below for usage).

As an example, an old drawing API function implementation would have
looked like this:

void frontend_draw_func(void *handle, ...)
{
    frontend *fe = (frontend *)handle;
    /* do stuff with fe */
}

After this change, that function would be rewritten as:

void frontend_draw_func(drawing *dr, ...)
{
    frontend *fe = GET_HANDLE_AS_TYPE(dr, frontend);
    /* do stuff with fe */
}

I have already made these changes to all the in-tree front ends, but
out-of-tree front ends will need to follow the procedure outlined
above.

Simon pointed out that changing the drawing API function pointer
signatures to take `drawing *` instead of `void *` results only in a
compiler warning, not an outright error. Thus, I've introduced a
version field to the beginning of the `drawing_api` struct, which will
cause a compilation error and hopefully force front ends to notice
this. This field should be set to 1 for now. Going forward, it will
provide a clear means of communicating future breaking API changes.
2024-08-15 08:45:59 +01:00

251 lines
6.1 KiB
C

/*
* fuzzpuzz.c: Fuzzing frontend to all puzzles.
*/
/*
* The idea here is that this front-end supports all back-ends and can
* feed them save files. It then asks the back-end to draw the puzzle
* (through a null drawing API) and reserialises the state. This
* tests the deserialiser, the code for loading game descriptions, the
* processing of move strings, the redraw code, and the serialisation
* routines, but is still pretty quick.
*
* To use AFL++ to drive fuzzpuzz, you can do something like:
*
* CC=afl-cc cmake -B build-afl
* cmake --build build-afl --target fuzzpuzz
* mkdir fuzz-in && ln icons/''*.sav fuzz-in
* afl-fuzz -i fuzz-in -o fuzz-out -x fuzzpuzz.dict -- build-afl/fuzzpuzz
*
* Similarly with Honggfuzz:
*
* CC=hfuzz-cc cmake -B build-honggfuzz
* cmake --build build-honggfuzz --target fuzzpuzz
* mkdir fuzz-corpus && ln icons/''*.sav fuzz-corpus
* honggfuzz -s -i fuzz-corpus -w fuzzpuzz.dict -- build-honggfuzz/fuzzpuzz
*
* You can also use libFuzzer, though it's not really a good fit for
* Puzzles. The experimental forking mode seems to work OK:
*
* CC=clang cmake -B build-clang -DWITH_LIBFUZZER=Y
* cmake --build build-clang --target fuzzpuzz
* mkdir fuzz-corpus && ln icons/''*.sav fuzz-corpus
* build-clang/fuzzpuzz -fork=1 -ignore_crashes=1 -dict=fuzzpuzz.dict \
* fuzz-corpus
*/
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#ifdef __AFL_FUZZ_TESTCASE_LEN
# include <unistd.h> /* read() is used by __AFL_FUZZ_TESTCASE_LEN. */
#endif
#include "puzzles.h"
#ifdef __AFL_FUZZ_INIT
__AFL_FUZZ_INIT();
#endif
#ifdef HAVE_HF_ITER
extern int HF_ITER(unsigned char **, size_t *);
#endif
/* This function is expected by libFuzzer. */
int LLVMFuzzerTestOneInput(unsigned char *data, size_t size);
static const char *fuzz_one(bool (*readfn)(void *, void *, int), void *rctx,
void (*rewindfn)(void *),
void (*writefn)(void *, const void *, int),
void *wctx)
{
const char *err;
char *gamename;
int i, w, h;
const game *ourgame = NULL;
static const drawing_api drapi = { 1, NULL };
midend *me;
err = identify_game(&gamename, readfn, rctx);
if (err != NULL) return err;
for (i = 0; i < gamecount; i++)
if (strcmp(gamename, gamelist[i]->name) == 0)
ourgame = gamelist[i];
sfree(gamename);
if (ourgame == NULL)
return "Game not recognised";
me = midend_new(NULL, ourgame, &drapi, NULL);
rewindfn(rctx);
err = midend_deserialise(me, readfn, rctx);
if (err != NULL) {
midend_free(me);
return err;
}
w = h = INT_MAX;
midend_size(me, &w, &h, false, 1);
midend_redraw(me);
midend_serialise(me, writefn, wctx);
midend_free(me);
return NULL;
}
#if defined(__AFL_FUZZ_TESTCASE_LEN) || defined(HAVE_HF_ITER) || \
!defined(OMIT_MAIN)
static void savefile_write(void *wctx, const void *buf, int len)
{
FILE *fp = (FILE *)wctx;
fwrite(buf, 1, len, fp);
}
#endif
struct memread {
const unsigned char *buf;
size_t pos;
size_t len;
};
static bool mem_read(void *wctx, void *buf, int len)
{
struct memread *ctx = wctx;
if (ctx->pos + len > ctx->len) return false;
memcpy(buf, ctx->buf + ctx->pos, len);
ctx->pos += len;
return true;
}
static void mem_rewind(void *wctx)
{
struct memread *ctx = wctx;
ctx->pos = 0;
}
static void null_write(void *wctx, const void *buf, int len)
{
}
int LLVMFuzzerTestOneInput(unsigned char *data, size_t size) {
struct memread ctx;
ctx.buf = data;
ctx.len = size;
ctx.pos = 0;
fuzz_one(mem_read, &ctx, mem_rewind, null_write, NULL);
return 0;
}
#if defined(__AFL_FUZZ_TESTCASE_LEN) || defined(HAVE_HF_ITER)
static const char *fuzz_one_mem(unsigned char *data, size_t size) {
struct memread ctx;
ctx.buf = data;
ctx.len = size;
ctx.pos = 0;
return fuzz_one(mem_read, &ctx, mem_rewind, savefile_write, stdout);
}
#endif
/*
* Three different versions of main(), for standalone, AFL, and
* Honggfuzz modes. LibFuzzer brings its own main().
*/
#ifdef OMIT_MAIN
/* Nothing. */
#elif defined(__AFL_FUZZ_TESTCASE_LEN)
/*
* AFL persistent mode, where we fuzz from a RAM buffer provided
* by AFL in a loop. This version can still be run standalone if
* necessary, for instance to diagnose a crash.
*/
int main(int argc, char **argv)
{
const char *err;
int ret;
if (argc != 1) {
fprintf(stderr, "usage: %s\n", argv[0]);
return 1;
}
#ifdef __AFL_HAVE_MANUAL_CONTROL
__AFL_INIT();
#endif
while (__AFL_LOOP(10000)) {
err = fuzz_one_mem(__AFL_FUZZ_TESTCASE_BUF, __AFL_FUZZ_TESTCASE_LEN);
if (err != NULL) {
fprintf(stderr, "%s\n", err);
ret = 1;
} else
ret = 0;
}
return ret;
}
#elif defined(HAVE_HF_ITER)
/*
* Honggfuzz persistent mode. Unlike AFL persistent mode, the
* resulting executable cannot be run outside of Honggfuzz.
*/
int main(int argc, char **argv)
{
if (argc != 1) {
fprintf(stderr, "usage: %s\n", argv[0]);
return 1;
}
while (true) {
unsigned char *testcase_buf;
size_t testcase_len;
HF_ITER(&testcase_buf, &testcase_len);
fuzz_one_mem(testcase_buf, testcase_len);
}
}
#else
/*
* Stand-alone mode: just handle a single test case on stdin.
*/
static bool savefile_read(void *wctx, void *buf, int len)
{
FILE *fp = (FILE *)wctx;
int ret;
ret = fread(buf, 1, len, fp);
return (ret == len);
}
static void savefile_rewind(void *wctx)
{
FILE *fp = (FILE *)wctx;
rewind(fp);
}
int main(int argc, char **argv)
{
const char *err;
if (argc != 1) {
fprintf(stderr, "usage: %s\n", argv[0]);
return 1;
}
/* Might in theory use this mode under AFL. */
#ifdef __AFL_HAVE_MANUAL_CONTROL
__AFL_INIT();
#endif
err = fuzz_one(savefile_read, stdin, savefile_rewind,
savefile_write, stdout);
if (err != NULL) {
fprintf(stderr, "%s\n", err);
return 1;
}
return 0;
}
#endif