option('tap_tests', type: 'feature', value: 'auto',
description: 'Enable TAP tests')
+option('fuzzing', type: 'boolean', value: false,
+ description: 'Build fuzz testing targets')
+
option('injection_points', type: 'boolean', value: false,
description: 'Enable injection points')
--- /dev/null
+/*-------------------------------------------------------------------------
+ *
+ * fuzz_b64decode.c
+ * Fuzzing harness for pg_b64_decode()
+ *
+ * Copyright (c) 2026, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ * src/test/fuzzing/fuzz_b64decode.c
+ *
+ * This harness feeds arbitrary byte sequences to pg_b64_decode(),
+ * which decodes base64-encoded data per RFC 4648. The function is
+ * a pure computation with no global state.
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres_fe.h"
+
+#include <stdio.h>
+
+#include "common/base64.h"
+
+int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size);
+
+int
+LLVMFuzzerTestOneInput(const uint8_t *data, size_t size)
+{
+ int dstlen;
+ uint8 *dst;
+
+ if (size == 0)
+ return 0;
+
+ /* Allocate a buffer large enough for any valid decoding */
+ dstlen = pg_b64_dec_len((int) size);
+ dst = malloc(dstlen);
+ if (!dst)
+ return 0;
+
+ (void) pg_b64_decode((const char *) data, (int) size, dst, dstlen);
+
+ free(dst);
+ return 0;
+}
+
+#ifdef STANDALONE_FUZZ_TARGET
+int
+main(int argc, char **argv)
+{
+ int i;
+ int ret = 0;
+
+ for (i = 1; i < argc; i++)
+ {
+ FILE *f = fopen(argv[i], "rb");
+ long len;
+ uint8_t *buf;
+ size_t n_read;
+
+ if (!f)
+ {
+ fprintf(stderr, "%s: could not open %s: %m\n", argv[0], argv[i]);
+ ret = 1;
+ continue;
+ }
+
+ fseek(f, 0, SEEK_END);
+ len = ftell(f);
+ fseek(f, 0, SEEK_SET);
+
+ if (len < 0)
+ {
+ fprintf(stderr, "%s: could not determine size of %s\n",
+ argv[0], argv[i]);
+ fclose(f);
+ ret = 1;
+ continue;
+ }
+
+ buf = malloc(len);
+ if (!buf)
+ {
+ fprintf(stderr, "%s: out of memory\n", argv[0]);
+ fclose(f);
+ return 1;
+ }
+
+ n_read = fread(buf, 1, len, f);
+ fclose(f);
+
+ LLVMFuzzerTestOneInput(buf, n_read);
+ free(buf);
+ }
+
+ return ret;
+}
+#endif /* STANDALONE_FUZZ_TARGET */
--- /dev/null
+/*-------------------------------------------------------------------------
+ *
+ * fuzz_conninfo.c
+ * Fuzzing harness for libpq connection string parsing
+ *
+ * Copyright (c) 2026, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ * src/test/fuzzing/fuzz_conninfo.c
+ *
+ * This harness feeds arbitrary byte sequences to PQconninfoParse(),
+ * which parses both key=value connection strings and PostgreSQL URIs
+ * (postgresql://...). The function is completely standalone and
+ * requires no database connection or other initialization.
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres_fe.h"
+
+#include <stdio.h>
+
+#include "libpq-fe.h"
+
+int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size);
+
+int
+LLVMFuzzerTestOneInput(const uint8_t *data, size_t size)
+{
+ char *str;
+ char *errmsg = NULL;
+ PQconninfoOption *opts;
+
+ if (size == 0)
+ return 0;
+
+ /* PQconninfoParse expects a NUL-terminated string */
+ str = malloc(size + 1);
+ if (!str)
+ return 0;
+ memcpy(str, data, size);
+ str[size] = '\0';
+
+ opts = PQconninfoParse(str, &errmsg);
+ if (opts)
+ PQconninfoFree(opts);
+ if (errmsg)
+ PQfreemem(errmsg);
+
+ free(str);
+ return 0;
+}
+
+#ifdef STANDALONE_FUZZ_TARGET
+int
+main(int argc, char **argv)
+{
+ int i;
+ int ret = 0;
+
+ for (i = 1; i < argc; i++)
+ {
+ FILE *f = fopen(argv[i], "rb");
+ long len;
+ uint8_t *buf;
+ size_t n_read;
+
+ if (!f)
+ {
+ fprintf(stderr, "%s: could not open %s: %m\n", argv[0], argv[i]);
+ ret = 1;
+ continue;
+ }
+
+ fseek(f, 0, SEEK_END);
+ len = ftell(f);
+ fseek(f, 0, SEEK_SET);
+
+ if (len < 0)
+ {
+ fprintf(stderr, "%s: could not determine size of %s\n",
+ argv[0], argv[i]);
+ fclose(f);
+ ret = 1;
+ continue;
+ }
+
+ buf = malloc(len);
+ if (!buf)
+ {
+ fprintf(stderr, "%s: out of memory\n", argv[0]);
+ fclose(f);
+ return 1;
+ }
+
+ n_read = fread(buf, 1, len, f);
+ fclose(f);
+
+ LLVMFuzzerTestOneInput(buf, n_read);
+ free(buf);
+ }
+
+ return ret;
+}
+#endif /* STANDALONE_FUZZ_TARGET */
--- /dev/null
+/*-------------------------------------------------------------------------
+ *
+ * fuzz_json.c
+ * Fuzzing harness for the non-incremental JSON parser
+ *
+ * Copyright (c) 2026, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ * src/test/fuzzing/fuzz_json.c
+ *
+ * This harness feeds arbitrary byte sequences to pg_parse_json() via
+ * makeJsonLexContextCstringLen(). It uses the null semantic action so
+ * that only lexing and structural validation are exercised.
+ *
+ * Build with a fuzzing engine (e.g. libFuzzer via -fsanitize=fuzzer)
+ * or in standalone mode, which reads files named on the command line.
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres_fe.h"
+
+#include <stdio.h>
+
+#include "common/jsonapi.h"
+#include "mb/pg_wchar.h"
+
+/*
+ * Entry point for libFuzzer and other engines that call
+ * LLVMFuzzerTestOneInput().
+ */
+int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size);
+
+int
+LLVMFuzzerTestOneInput(const uint8_t *data, size_t size)
+{
+ JsonLexContext lex;
+
+ if (size == 0)
+ return 0;
+
+ makeJsonLexContextCstringLen(&lex, (const char *) data, size,
+ PG_UTF8, true);
+ setJsonLexContextOwnsTokens(&lex, true);
+
+ (void) pg_parse_json(&lex, &nullSemAction);
+
+ freeJsonLexContext(&lex);
+
+ return 0;
+}
+
+#ifdef STANDALONE_FUZZ_TARGET
+int
+main(int argc, char **argv)
+{
+ int i;
+ int ret = 0;
+
+ for (i = 1; i < argc; i++)
+ {
+ FILE *f = fopen(argv[i], "rb");
+ long len;
+ uint8_t *buf;
+ size_t n_read;
+
+ if (!f)
+ {
+ fprintf(stderr, "%s: could not open %s: %m\n", argv[0], argv[i]);
+ ret = 1;
+ continue;
+ }
+
+ fseek(f, 0, SEEK_END);
+ len = ftell(f);
+ fseek(f, 0, SEEK_SET);
+
+ if (len < 0)
+ {
+ fprintf(stderr, "%s: could not determine size of %s\n",
+ argv[0], argv[i]);
+ fclose(f);
+ ret = 1;
+ continue;
+ }
+
+ buf = malloc(len);
+ if (!buf)
+ {
+ fprintf(stderr, "%s: out of memory\n", argv[0]);
+ fclose(f);
+ return 1;
+ }
+
+ n_read = fread(buf, 1, len, f);
+ fclose(f);
+
+ LLVMFuzzerTestOneInput(buf, n_read);
+ free(buf);
+ }
+
+ return ret;
+}
+#endif /* STANDALONE_FUZZ_TARGET */
--- /dev/null
+/*-------------------------------------------------------------------------
+ *
+ * fuzz_json_incremental.c
+ * Fuzzing harness for the incremental JSON parser
+ *
+ * Copyright (c) 2026, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ * src/test/fuzzing/fuzz_json_incremental.c
+ *
+ * This harness feeds arbitrary byte sequences to
+ * pg_parse_json_incremental() in small chunks, exercising the
+ * incremental lexer's boundary handling. The first byte of the input
+ * is used to vary the chunk size so that the fuzzer can explore
+ * different splitting strategies.
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres_fe.h"
+
+#include <stdio.h>
+
+#include "common/jsonapi.h"
+#include "mb/pg_wchar.h"
+
+int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size);
+
+int
+LLVMFuzzerTestOneInput(const uint8_t *data, size_t size)
+{
+ JsonLexContext lex;
+ size_t chunk_size;
+ size_t offset;
+
+ if (size < 2)
+ return 0;
+
+ /*
+ * Use the first byte to select a chunk size between 1 and 128. This lets
+ * the fuzzer explore different ways of splitting the same input across
+ * incremental parse calls.
+ */
+ chunk_size = (data[0] % 128) + 1;
+ data++;
+ size--;
+
+ makeJsonLexContextIncremental(&lex, PG_UTF8, true);
+ setJsonLexContextOwnsTokens(&lex, true);
+
+ offset = 0;
+ while (offset < size)
+ {
+ size_t remaining = size - offset;
+ size_t to_feed = (remaining < chunk_size) ? remaining : chunk_size;
+ bool is_last = (offset + to_feed >= size);
+ JsonParseErrorType result;
+
+ result = pg_parse_json_incremental(&lex, &nullSemAction,
+ (const char *) data + offset,
+ to_feed, is_last);
+
+ offset += to_feed;
+
+ if (result != JSON_SUCCESS && result != JSON_INCOMPLETE)
+ break;
+ if (result == JSON_SUCCESS)
+ break;
+ }
+
+ freeJsonLexContext(&lex);
+
+ return 0;
+}
+
+#ifdef STANDALONE_FUZZ_TARGET
+int
+main(int argc, char **argv)
+{
+ int i;
+ int ret = 0;
+
+ for (i = 1; i < argc; i++)
+ {
+ FILE *f = fopen(argv[i], "rb");
+ long len;
+ uint8_t *buf;
+ size_t n_read;
+
+ if (!f)
+ {
+ fprintf(stderr, "%s: could not open %s: %m\n", argv[0], argv[i]);
+ ret = 1;
+ continue;
+ }
+
+ fseek(f, 0, SEEK_END);
+ len = ftell(f);
+ fseek(f, 0, SEEK_SET);
+
+ if (len < 0)
+ {
+ fprintf(stderr, "%s: could not determine size of %s\n",
+ argv[0], argv[i]);
+ fclose(f);
+ ret = 1;
+ continue;
+ }
+
+ buf = malloc(len);
+ if (!buf)
+ {
+ fprintf(stderr, "%s: out of memory\n", argv[0]);
+ fclose(f);
+ return 1;
+ }
+
+ n_read = fread(buf, 1, len, f);
+ fclose(f);
+
+ LLVMFuzzerTestOneInput(buf, n_read);
+ free(buf);
+ }
+
+ return ret;
+}
+#endif /* STANDALONE_FUZZ_TARGET */
--- /dev/null
+/*-------------------------------------------------------------------------
+ *
+ * fuzz_parsepgarray.c
+ * Fuzzing harness for parsePGArray()
+ *
+ * Copyright (c) 2026, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ * src/test/fuzzing/fuzz_parsepgarray.c
+ *
+ * This harness feeds arbitrary byte sequences to parsePGArray(),
+ * which parses PostgreSQL array literal syntax ({elem,"elem",...})
+ * including nested arrays, quoted elements, and backslash escaping.
+ * The function is standalone and requires no database connection.
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres_fe.h"
+
+#include <stdio.h>
+
+#include "fe_utils/string_utils.h"
+
+int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size);
+
+int
+LLVMFuzzerTestOneInput(const uint8_t *data, size_t size)
+{
+ char *str;
+ char **items = NULL;
+ int nitems = 0;
+
+ if (size == 0)
+ return 0;
+
+ /* parsePGArray expects a NUL-terminated string */
+ str = malloc(size + 1);
+ if (!str)
+ return 0;
+ memcpy(str, data, size);
+ str[size] = '\0';
+
+ (void) parsePGArray(str, &items, &nitems);
+ free(items);
+
+ free(str);
+ return 0;
+}
+
+#ifdef STANDALONE_FUZZ_TARGET
+int
+main(int argc, char **argv)
+{
+ int i;
+ int ret = 0;
+
+ for (i = 1; i < argc; i++)
+ {
+ FILE *f = fopen(argv[i], "rb");
+ long len;
+ uint8_t *buf;
+ size_t n_read;
+
+ if (!f)
+ {
+ fprintf(stderr, "%s: could not open %s: %m\n", argv[0], argv[i]);
+ ret = 1;
+ continue;
+ }
+
+ fseek(f, 0, SEEK_END);
+ len = ftell(f);
+ fseek(f, 0, SEEK_SET);
+
+ if (len < 0)
+ {
+ fprintf(stderr, "%s: could not determine size of %s\n",
+ argv[0], argv[i]);
+ fclose(f);
+ ret = 1;
+ continue;
+ }
+
+ buf = malloc(len);
+ if (!buf)
+ {
+ fprintf(stderr, "%s: out of memory\n", argv[0]);
+ fclose(f);
+ return 1;
+ }
+
+ n_read = fread(buf, 1, len, f);
+ fclose(f);
+
+ LLVMFuzzerTestOneInput(buf, n_read);
+ free(buf);
+ }
+
+ return ret;
+}
+#endif /* STANDALONE_FUZZ_TARGET */
--- /dev/null
+/*-------------------------------------------------------------------------
+ *
+ * fuzz_pgbench_expr.c
+ * Fuzzing harness for the pgbench expression parser
+ *
+ * Copyright (c) 2026, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ * src/test/fuzzing/fuzz_pgbench_expr.c
+ *
+ * This harness feeds arbitrary byte sequences to the pgbench expression
+ * parser (expr_yyparse). The parser exercises a Bison grammar and Flex
+ * lexer that handle arithmetic expressions, function calls, boolean
+ * operators, and CASE expressions.
+ *
+ * The pgbench expression parser normally calls syntax_error() on any
+ * parse error, which calls exit(1). This harness provides replacement
+ * definitions of syntax_error(), strtoint64(), and strtodouble() so
+ * that the generated parser and lexer objects can link without pulling
+ * in pgbench.c. Our syntax_error() uses longjmp to recover rather
+ * than exiting.
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres_fe.h"
+
+#include <setjmp.h>
+#include <stdio.h>
+
+#include "pgbench.h"
+#include "fe_utils/psqlscan.h"
+
+static sigjmp_buf fuzz_jmp_buf;
+
+static void free_pgbench_expr(PgBenchExpr *expr);
+
+static const PsqlScanCallbacks fuzz_callbacks = {
+ NULL, /* no variable lookup needed */
+};
+
+int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size);
+
+/*
+ * Replacement for pgbench.c's syntax_error(). Instead of calling exit(),
+ * we longjmp back to the fuzzer's recovery point.
+ */
+void
+syntax_error(const char *source, int lineno, const char *line,
+ const char *command, const char *msg,
+ const char *more, int column)
+{
+ siglongjmp(fuzz_jmp_buf, 1);
+}
+
+/*
+ * Replacement for pgbench.c's strtoint64().
+ */
+bool
+strtoint64(const char *str, bool errorOK, int64 *result)
+{
+ char *end;
+
+ errno = 0;
+ *result = strtoi64(str, &end, 10);
+
+ if (errno == ERANGE || errno != 0 || end == str || *end != '\0')
+ return false;
+ return true;
+}
+
+/*
+ * Replacement for pgbench.c's strtodouble().
+ */
+bool
+strtodouble(const char *str, bool errorOK, double *dv)
+{
+ char *end;
+
+ errno = 0;
+ *dv = strtod(str, &end);
+
+ if (errno == ERANGE || errno != 0 || end == str || *end != '\0')
+ return false;
+ return true;
+}
+
+/*
+ * Recursively free a PgBenchExpr tree.
+ */
+static void
+free_pgbench_expr(PgBenchExpr *expr)
+{
+ PgBenchExprLink *link;
+ PgBenchExprLink *next;
+
+ if (expr == NULL)
+ return;
+
+ switch (expr->etype)
+ {
+ case ENODE_CONSTANT:
+ break;
+ case ENODE_VARIABLE:
+ pg_free(expr->u.variable.varname);
+ break;
+ case ENODE_FUNCTION:
+ for (link = expr->u.function.args; link != NULL; link = next)
+ {
+ next = link->next;
+ free_pgbench_expr(link->expr);
+ pg_free(link);
+ }
+ break;
+ }
+
+ pg_free(expr);
+}
+
+int
+LLVMFuzzerTestOneInput(const uint8_t *data, size_t size)
+{
+ char *str;
+ PsqlScanState sstate;
+ yyscan_t yyscanner;
+ PgBenchExpr *result = NULL;
+
+ if (size == 0)
+ return 0;
+
+ /* expr_yyparse needs a NUL-terminated string */
+ str = malloc(size + 1);
+ if (!str)
+ return 0;
+ memcpy(str, data, size);
+ str[size] = '\0';
+
+ sstate = psql_scan_create(&fuzz_callbacks);
+ psql_scan_setup(sstate, str, (int) size, 0, true);
+
+ yyscanner = expr_scanner_init(sstate, "fuzz", 1, 0, "\\set");
+
+ if (sigsetjmp(fuzz_jmp_buf, 0) == 0)
+ {
+ (void) expr_yyparse(&result, yyscanner);
+ }
+
+ /* Clean up regardless of success or longjmp */
+ expr_scanner_finish(yyscanner);
+ psql_scan_finish(sstate);
+ psql_scan_destroy(sstate);
+
+ if (result)
+ free_pgbench_expr(result);
+
+ free(str);
+ return 0;
+}
+
+#ifdef STANDALONE_FUZZ_TARGET
+int
+main(int argc, char **argv)
+{
+ int i;
+ int ret = 0;
+
+ for (i = 1; i < argc; i++)
+ {
+ FILE *f = fopen(argv[i], "rb");
+ long len;
+ uint8_t *buf;
+ size_t n_read;
+
+ if (!f)
+ {
+ fprintf(stderr, "%s: could not open %s: %m\n", argv[0], argv[i]);
+ ret = 1;
+ continue;
+ }
+
+ fseek(f, 0, SEEK_END);
+ len = ftell(f);
+ fseek(f, 0, SEEK_SET);
+
+ if (len < 0)
+ {
+ fprintf(stderr, "%s: could not determine size of %s\n",
+ argv[0], argv[i]);
+ fclose(f);
+ ret = 1;
+ continue;
+ }
+
+ buf = malloc(len);
+ if (!buf)
+ {
+ fprintf(stderr, "%s: out of memory\n", argv[0]);
+ fclose(f);
+ return 1;
+ }
+
+ n_read = fread(buf, 1, len, f);
+ fclose(f);
+
+ LLVMFuzzerTestOneInput(buf, n_read);
+ free(buf);
+ }
+
+ return ret;
+}
+#endif /* STANDALONE_FUZZ_TARGET */
--- /dev/null
+/*-------------------------------------------------------------------------
+ *
+ * fuzz_pglz.c
+ * Fuzzing harness for the PostgreSQL LZ decompressor
+ *
+ * Copyright (c) 2026, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ * src/test/fuzzing/fuzz_pglz.c
+ *
+ * This harness feeds arbitrary byte sequences to pglz_decompress(),
+ * which decompresses PostgreSQL's native LZ-compressed data. The
+ * decompressor is a pure function with no global state, making it
+ * ideal for fuzzing.
+ *
+ * The first 4 bytes of the fuzzer input are interpreted as the
+ * claimed raw (uncompressed) size in little-endian byte order,
+ * capped at 1 MB. The remaining bytes are the compressed payload.
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres_fe.h"
+
+#include <stdio.h>
+#include <string.h>
+
+#include "common/pg_lzcompress.h"
+
+#define MAX_RAW_SIZE (1024 * 1024)
+
+int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size);
+
+int
+LLVMFuzzerTestOneInput(const uint8_t *data, size_t size)
+{
+ int32 rawsize;
+ char *dest;
+
+ /* Need at least 4 bytes for the raw size, plus some compressed data */
+ if (size < 5)
+ return 0;
+
+ /* Extract claimed raw size from first 4 bytes (little-endian) */
+ rawsize = (int32) data[0] |
+ ((int32) data[1] << 8) |
+ ((int32) data[2] << 16) |
+ ((int32) data[3] << 24);
+
+ /* Reject nonsensical sizes */
+ if (rawsize <= 0 || rawsize > MAX_RAW_SIZE)
+ return 0;
+
+ dest = malloc(rawsize);
+ if (!dest)
+ return 0;
+
+ /* Try decompression with completeness check */
+ (void) pglz_decompress((const char *) data + 4,
+ (int32) (size - 4),
+ dest,
+ rawsize,
+ true);
+
+ /* Also try without completeness check to exercise that path */
+ (void) pglz_decompress((const char *) data + 4,
+ (int32) (size - 4),
+ dest,
+ rawsize,
+ false);
+
+ free(dest);
+ return 0;
+}
+
+#ifdef STANDALONE_FUZZ_TARGET
+int
+main(int argc, char **argv)
+{
+ int i;
+ int ret = 0;
+
+ for (i = 1; i < argc; i++)
+ {
+ FILE *f = fopen(argv[i], "rb");
+ long len;
+ uint8_t *buf;
+ size_t n_read;
+
+ if (!f)
+ {
+ fprintf(stderr, "%s: could not open %s: %m\n", argv[0], argv[i]);
+ ret = 1;
+ continue;
+ }
+
+ fseek(f, 0, SEEK_END);
+ len = ftell(f);
+ fseek(f, 0, SEEK_SET);
+
+ if (len < 0)
+ {
+ fprintf(stderr, "%s: could not determine size of %s\n",
+ argv[0], argv[i]);
+ fclose(f);
+ ret = 1;
+ continue;
+ }
+
+ buf = malloc(len);
+ if (!buf)
+ {
+ fprintf(stderr, "%s: out of memory\n", argv[0]);
+ fclose(f);
+ return 1;
+ }
+
+ n_read = fread(buf, 1, len, f);
+ fclose(f);
+
+ LLVMFuzzerTestOneInput(buf, n_read);
+ free(buf);
+ }
+
+ return ret;
+}
+#endif /* STANDALONE_FUZZ_TARGET */
--- /dev/null
+/*-------------------------------------------------------------------------
+ *
+ * fuzz_rawparser.c
+ * Fuzzing harness for the PostgreSQL raw SQL parser
+ *
+ * Copyright (c) 2026, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ * src/test/fuzzing/fuzz_rawparser.c
+ *
+ * This harness feeds arbitrary byte sequences to raw_parser(), which
+ * performs lexical and grammatical analysis of SQL statements. It
+ * performs minimal backend initialization (just the memory-context
+ * subsystem) and catches all parser errors via PG_TRY/PG_CATCH.
+ *
+ * The harness links against postgres_lib using archive semantics.
+ * It provides stub definitions for symbols normally supplied by
+ * main/main.c (progname, parse_dispatch_option) so that the linker
+ * does not pull in main.o and conflict with the harness's own main().
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include <stdio.h>
+
+#include "mb/pg_wchar.h"
+#include "miscadmin.h"
+#include "parser/parser.h"
+#include "postmaster/postmaster.h"
+#include "utils/memutils.h"
+#include "utils/palloc.h"
+
+/*
+ * Stub definitions for symbols that main/main.c normally provides.
+ * By defining them here we prevent the archive linker from pulling in
+ * main.o (which defines its own main()).
+ */
+const char *progname = "fuzz_rawparser";
+
+DispatchOption
+parse_dispatch_option(const char *name)
+{
+ return DISPATCH_POSTMASTER;
+}
+
+static bool initialized = false;
+
+static void fuzz_initialize(void);
+
+int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size);
+
+/*
+ * One-time initialization: set up memory contexts and encoding.
+ */
+static void
+fuzz_initialize(void)
+{
+ MemoryContextInit();
+ SetDatabaseEncoding(PG_UTF8);
+ SetMessageEncoding(PG_UTF8);
+}
+
+int
+LLVMFuzzerTestOneInput(const uint8_t *data, size_t size)
+{
+ char *str;
+ MemoryContext fuzz_context;
+ MemoryContext oldcontext;
+
+ if (!initialized)
+ {
+ fuzz_initialize();
+ initialized = true;
+ }
+
+ if (size == 0)
+ return 0;
+
+ /*
+ * Create a temporary memory context for each parse attempt so that all
+ * allocations made by the parser are freed afterwards.
+ */
+ fuzz_context = AllocSetContextCreate(TopMemoryContext,
+ "Fuzz Context",
+ ALLOCSET_DEFAULT_SIZES);
+ oldcontext = MemoryContextSwitchTo(fuzz_context);
+
+ /* raw_parser() expects a NUL-terminated string */
+ str = palloc(size + 1);
+ memcpy(str, data, size);
+ str[size] = '\0';
+
+ PG_TRY();
+ {
+ (void) raw_parser(str, RAW_PARSE_DEFAULT);
+ }
+ PG_CATCH();
+ {
+ FlushErrorState();
+ }
+ PG_END_TRY();
+
+ MemoryContextSwitchTo(oldcontext);
+ MemoryContextDelete(fuzz_context);
+
+ return 0;
+}
+
+#ifdef STANDALONE_FUZZ_TARGET
+int
+main(int argc, char **argv)
+{
+ int i;
+ int ret = 0;
+
+ for (i = 1; i < argc; i++)
+ {
+ FILE *f = fopen(argv[i], "rb");
+ long len;
+ uint8_t *buf;
+ size_t n_read;
+
+ if (!f)
+ {
+ fprintf(stderr, "%s: could not open %s: %m\n", argv[0], argv[i]);
+ ret = 1;
+ continue;
+ }
+
+ fseek(f, 0, SEEK_END);
+ len = ftell(f);
+ fseek(f, 0, SEEK_SET);
+
+ if (len < 0)
+ {
+ fprintf(stderr, "%s: could not determine size of %s\n",
+ argv[0], argv[i]);
+ fclose(f);
+ ret = 1;
+ continue;
+ }
+
+ buf = malloc(len);
+ if (!buf)
+ {
+ fprintf(stderr, "%s: out of memory\n", argv[0]);
+ fclose(f);
+ return 1;
+ }
+
+ n_read = fread(buf, 1, len, f);
+ fclose(f);
+
+ LLVMFuzzerTestOneInput(buf, n_read);
+ free(buf);
+ }
+
+ return ret;
+}
+#endif /* STANDALONE_FUZZ_TARGET */
--- /dev/null
+/*-------------------------------------------------------------------------
+ *
+ * fuzz_regex.c
+ * Fuzzing harness for the PostgreSQL regular expression engine
+ *
+ * Copyright (c) 2026, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ * src/test/fuzzing/fuzz_regex.c
+ *
+ * This harness feeds arbitrary byte sequences to pg_regcomp() and
+ * pg_regexec(), exercising the full POSIX/ARE regex compiler and
+ * executor. The first byte selects regex flags; the remaining bytes
+ * are split between the regex pattern and a test subject string.
+ *
+ * The harness links against postgres_lib using archive semantics.
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include <stdio.h>
+
+#include "catalog/pg_collation.h"
+#include "mb/pg_wchar.h"
+#include "miscadmin.h"
+#include "postmaster/postmaster.h"
+#include "regex/regex.h"
+#include "utils/memutils.h"
+#include "utils/palloc.h"
+
+/* Stubs for symbols from main/main.c */
+const char *progname = "fuzz_regex";
+
+DispatchOption
+parse_dispatch_option(const char *name)
+{
+ return DISPATCH_POSTMASTER;
+}
+
+static bool initialized = false;
+
+static void fuzz_initialize(void);
+
+int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size);
+
+static void
+fuzz_initialize(void)
+{
+ MemoryContextInit();
+ SetDatabaseEncoding(PG_UTF8);
+ SetMessageEncoding(PG_UTF8);
+}
+
+int
+LLVMFuzzerTestOneInput(const uint8_t *data, size_t size)
+{
+ uint8_t flags_byte;
+ int re_flags;
+ size_t pat_len;
+ size_t subj_len;
+ const char *pat_start;
+ const char *subj_start;
+ pg_wchar *pat_wchar;
+ pg_wchar *subj_wchar;
+ int pat_wlen;
+ int subj_wlen;
+ regex_t re;
+ regmatch_t matches[10];
+ MemoryContext fuzz_context;
+ MemoryContext oldcontext;
+
+ if (!initialized)
+ {
+ fuzz_initialize();
+ initialized = true;
+ }
+
+ /* Need at least flags byte + 1 byte of pattern */
+ if (size < 2)
+ return 0;
+
+ /*
+ * First byte selects regex flags. We map bits to useful flag combinations
+ * to get good coverage of different regex modes.
+ */
+ flags_byte = data[0];
+ re_flags = REG_ADVANCED;
+ if (flags_byte & 0x01)
+ re_flags = REG_EXTENDED; /* ERE instead of ARE */
+ if (flags_byte & 0x02)
+ re_flags |= REG_ICASE;
+ if (flags_byte & 0x04)
+ re_flags |= REG_NEWLINE;
+ if (flags_byte & 0x08)
+ re_flags |= REG_NOSUB;
+
+ data++;
+ size--;
+
+ /* Split remaining input: first half pattern, second half subject */
+ pat_len = size / 2;
+ if (pat_len == 0)
+ pat_len = 1;
+ subj_len = size - pat_len;
+
+ pat_start = (const char *) data;
+ subj_start = (const char *) data + pat_len;
+
+ fuzz_context = AllocSetContextCreate(TopMemoryContext,
+ "Fuzz Context",
+ ALLOCSET_DEFAULT_SIZES);
+ oldcontext = MemoryContextSwitchTo(fuzz_context);
+
+ /* Convert to pg_wchar for the regex API */
+ pat_wchar = palloc((pat_len + 1) * sizeof(pg_wchar));
+ pat_wlen = pg_mb2wchar_with_len(pat_start, pat_wchar, (int) pat_len);
+
+ if (pg_regcomp(&re, pat_wchar, pat_wlen, re_flags, C_COLLATION_OID) == 0)
+ {
+ /* Compile succeeded — try executing against the subject */
+ if (subj_len > 0)
+ {
+ subj_wchar = palloc((subj_len + 1) * sizeof(pg_wchar));
+ subj_wlen = pg_mb2wchar_with_len(subj_start, subj_wchar,
+ (int) subj_len);
+
+ (void) pg_regexec(&re, subj_wchar, subj_wlen, 0, NULL,
+ lengthof(matches), matches, 0);
+ }
+
+ pg_regfree(&re);
+ }
+
+ MemoryContextSwitchTo(oldcontext);
+ MemoryContextDelete(fuzz_context);
+
+ return 0;
+}
+
+#ifdef STANDALONE_FUZZ_TARGET
+int
+main(int argc, char **argv)
+{
+ int i;
+ int ret = 0;
+
+ for (i = 1; i < argc; i++)
+ {
+ FILE *f = fopen(argv[i], "rb");
+ long len;
+ uint8_t *buf;
+ size_t n_read;
+
+ if (!f)
+ {
+ fprintf(stderr, "%s: could not open %s: %m\n", argv[0], argv[i]);
+ ret = 1;
+ continue;
+ }
+
+ fseek(f, 0, SEEK_END);
+ len = ftell(f);
+ fseek(f, 0, SEEK_SET);
+
+ if (len < 0)
+ {
+ fprintf(stderr, "%s: could not determine size of %s\n",
+ argv[0], argv[i]);
+ fclose(f);
+ ret = 1;
+ continue;
+ }
+
+ buf = malloc(len);
+ if (!buf)
+ {
+ fprintf(stderr, "%s: out of memory\n", argv[0]);
+ fclose(f);
+ return 1;
+ }
+
+ n_read = fread(buf, 1, len, f);
+ fclose(f);
+
+ LLVMFuzzerTestOneInput(buf, n_read);
+ free(buf);
+ }
+
+ return ret;
+}
+#endif /* STANDALONE_FUZZ_TARGET */
--- /dev/null
+/*-------------------------------------------------------------------------
+ *
+ * fuzz_saslprep.c
+ * Fuzzing harness for pg_saslprep()
+ *
+ * Copyright (c) 2026, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ * src/test/fuzzing/fuzz_saslprep.c
+ *
+ * This harness feeds arbitrary byte sequences to pg_saslprep(),
+ * which performs SASLprep normalization (RFC 4013) on UTF-8 strings.
+ * This involves Unicode NFKC normalization, character mapping, and
+ * prohibited character detection. The function is standalone and
+ * requires no database connection.
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres_fe.h"
+
+#include <stdio.h>
+
+#include "common/saslprep.h"
+
+int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size);
+
+int
+LLVMFuzzerTestOneInput(const uint8_t *data, size_t size)
+{
+ char *str;
+ char *output = NULL;
+
+ if (size == 0)
+ return 0;
+
+ /* pg_saslprep expects a NUL-terminated string */
+ str = malloc(size + 1);
+ if (!str)
+ return 0;
+ memcpy(str, data, size);
+ str[size] = '\0';
+
+ (void) pg_saslprep(str, &output);
+
+ if (output)
+ free(output);
+
+ free(str);
+ return 0;
+}
+
+#ifdef STANDALONE_FUZZ_TARGET
+int
+main(int argc, char **argv)
+{
+ int i;
+ int ret = 0;
+
+ for (i = 1; i < argc; i++)
+ {
+ FILE *f = fopen(argv[i], "rb");
+ long len;
+ uint8_t *buf;
+ size_t n_read;
+
+ if (!f)
+ {
+ fprintf(stderr, "%s: could not open %s: %m\n", argv[0], argv[i]);
+ ret = 1;
+ continue;
+ }
+
+ fseek(f, 0, SEEK_END);
+ len = ftell(f);
+ fseek(f, 0, SEEK_SET);
+
+ if (len < 0)
+ {
+ fprintf(stderr, "%s: could not determine size of %s\n",
+ argv[0], argv[i]);
+ fclose(f);
+ ret = 1;
+ continue;
+ }
+
+ buf = malloc(len);
+ if (!buf)
+ {
+ fprintf(stderr, "%s: out of memory\n", argv[0]);
+ fclose(f);
+ return 1;
+ }
+
+ n_read = fread(buf, 1, len, f);
+ fclose(f);
+
+ LLVMFuzzerTestOneInput(buf, n_read);
+ free(buf);
+ }
+
+ return ret;
+}
+#endif /* STANDALONE_FUZZ_TARGET */
--- /dev/null
+/*-------------------------------------------------------------------------
+ *
+ * fuzz_typeinput.c
+ * Fuzzing harness for PostgreSQL type input functions
+ *
+ * Copyright (c) 2026, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ * src/test/fuzzing/fuzz_typeinput.c
+ *
+ * This harness feeds arbitrary byte sequences to the backend's type
+ * input functions: numeric_in, date_in, timestamp_in, timestamptz_in,
+ * and interval_in. These functions parse textual representations of
+ * data types and are a key part of PostgreSQL's input validation.
+ *
+ * The first byte of input selects which type parser to call; the
+ * remaining bytes are the type-input string. All functions support
+ * soft error handling via ErrorSaveContext, so errors are caught
+ * without ereport/PG_TRY. PG_TRY/PG_CATCH is used as a safety net
+ * for any unexpected hard errors.
+ *
+ * The harness links against postgres_lib using archive semantics.
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include <stdio.h>
+
+#include "fmgr.h"
+#include "mb/pg_wchar.h"
+#include "miscadmin.h"
+#include "nodes/miscnodes.h"
+#include "pgtime.h"
+#include "postmaster/postmaster.h"
+#include "utils/builtins.h"
+#include "utils/datetime.h"
+#include "utils/memutils.h"
+#include "utils/numeric.h"
+#include "utils/palloc.h"
+#include "utils/timestamp.h"
+
+/* Stubs for symbols from main/main.c */
+const char *progname = "fuzz_typeinput";
+
+DispatchOption
+parse_dispatch_option(const char *name)
+{
+ return DISPATCH_POSTMASTER;
+}
+
+/* Type selector values */
+#define FUZZ_NUMERIC 0
+#define FUZZ_DATE 1
+#define FUZZ_TIMESTAMP 2
+#define FUZZ_TIMESTAMPTZ 3
+#define FUZZ_INTERVAL 4
+#define FUZZ_NTYPES 5
+
+static bool initialized = false;
+
+static void fuzz_initialize(void);
+
+int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size);
+
+static void
+fuzz_initialize(void)
+{
+ MemoryContextInit();
+ SetDatabaseEncoding(PG_UTF8);
+ SetMessageEncoding(PG_UTF8);
+
+ /*
+ * Initialize timezone subsystem. Use "GMT" because it is resolved
+ * without filesystem access (the timezone data directory may not exist in
+ * a fuzzing build).
+ */
+ pg_timezone_initialize();
+}
+
+int
+LLVMFuzzerTestOneInput(const uint8_t *data, size_t size)
+{
+ char *str;
+ int type_sel;
+
+ LOCAL_FCINFO(fcinfo, 3);
+ ErrorSaveContext escontext;
+ MemoryContext fuzz_context;
+ MemoryContext oldcontext;
+
+ if (!initialized)
+ {
+ fuzz_initialize();
+ initialized = true;
+ }
+
+ /* Need at least type selector + 1 byte of input */
+ if (size < 2)
+ return 0;
+
+ type_sel = data[0] % FUZZ_NTYPES;
+ data++;
+ size--;
+
+ fuzz_context = AllocSetContextCreate(TopMemoryContext,
+ "Fuzz Context",
+ ALLOCSET_DEFAULT_SIZES);
+ oldcontext = MemoryContextSwitchTo(fuzz_context);
+
+ /* Build a NUL-terminated string from the input */
+ str = palloc(size + 1);
+ memcpy(str, data, size);
+ str[size] = '\0';
+
+ /* Set up ErrorSaveContext for soft error handling */
+ memset(&escontext, 0, sizeof(escontext));
+ escontext.type = T_ErrorSaveContext;
+ escontext.error_occurred = false;
+ escontext.details_wanted = false;
+
+ /* Set up FunctionCallInfo */
+ memset(fcinfo, 0, SizeForFunctionCallInfo(3));
+ fcinfo->nargs = 3;
+ fcinfo->args[0].value = CStringGetDatum(str);
+ fcinfo->args[0].isnull = false;
+ fcinfo->args[1].value = ObjectIdGetDatum(InvalidOid); /* typelem */
+ fcinfo->args[1].isnull = false;
+ fcinfo->args[2].value = Int32GetDatum(-1); /* typmod */
+ fcinfo->args[2].isnull = false;
+ fcinfo->context = (Node *) &escontext;
+
+ PG_TRY();
+ {
+ switch (type_sel)
+ {
+ case FUZZ_NUMERIC:
+ (void) numeric_in(fcinfo);
+ break;
+ case FUZZ_DATE:
+ (void) date_in(fcinfo);
+ break;
+ case FUZZ_TIMESTAMP:
+ (void) timestamp_in(fcinfo);
+ break;
+ case FUZZ_TIMESTAMPTZ:
+ (void) timestamptz_in(fcinfo);
+ break;
+ case FUZZ_INTERVAL:
+ (void) interval_in(fcinfo);
+ break;
+ }
+ }
+ PG_CATCH();
+ {
+ FlushErrorState();
+ }
+ PG_END_TRY();
+
+ MemoryContextSwitchTo(oldcontext);
+ MemoryContextDelete(fuzz_context);
+
+ return 0;
+}
+
+#ifdef STANDALONE_FUZZ_TARGET
+int
+main(int argc, char **argv)
+{
+ int i;
+ int ret = 0;
+
+ for (i = 1; i < argc; i++)
+ {
+ FILE *f = fopen(argv[i], "rb");
+ long len;
+ uint8_t *buf;
+ size_t n_read;
+
+ if (!f)
+ {
+ fprintf(stderr, "%s: could not open %s: %m\n", argv[0], argv[i]);
+ ret = 1;
+ continue;
+ }
+
+ fseek(f, 0, SEEK_END);
+ len = ftell(f);
+ fseek(f, 0, SEEK_SET);
+
+ if (len < 0)
+ {
+ fprintf(stderr, "%s: could not determine size of %s\n",
+ argv[0], argv[i]);
+ fclose(f);
+ ret = 1;
+ continue;
+ }
+
+ buf = malloc(len);
+ if (!buf)
+ {
+ fprintf(stderr, "%s: out of memory\n", argv[0]);
+ fclose(f);
+ return 1;
+ }
+
+ n_read = fread(buf, 1, len, f);
+ fclose(f);
+
+ LLVMFuzzerTestOneInput(buf, n_read);
+ free(buf);
+ }
+
+ return ret;
+}
+#endif /* STANDALONE_FUZZ_TARGET */
--- /dev/null
+/*-------------------------------------------------------------------------
+ *
+ * fuzz_unescapebytea.c
+ * Fuzzing harness for PQunescapeBytea()
+ *
+ * Copyright (c) 2026, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ * src/test/fuzzing/fuzz_unescapebytea.c
+ *
+ * This harness feeds arbitrary byte sequences to PQunescapeBytea(),
+ * which decodes bytea escape formats: hex (\xDEAD...) and legacy
+ * backslash-octal (\352\273\276...). The function is completely
+ * standalone and requires no database connection.
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres_fe.h"
+
+#include <stdio.h>
+
+#include "libpq-fe.h"
+
+int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size);
+
+int
+LLVMFuzzerTestOneInput(const uint8_t *data, size_t size)
+{
+ char *str;
+ size_t resultlen;
+ unsigned char *result;
+
+ if (size == 0)
+ return 0;
+
+ /* PQunescapeBytea expects a NUL-terminated string */
+ str = malloc(size + 1);
+ if (!str)
+ return 0;
+ memcpy(str, data, size);
+ str[size] = '\0';
+
+ result = PQunescapeBytea((const unsigned char *) str, &resultlen);
+ if (result)
+ PQfreemem(result);
+
+ free(str);
+ return 0;
+}
+
+#ifdef STANDALONE_FUZZ_TARGET
+int
+main(int argc, char **argv)
+{
+ int i;
+ int ret = 0;
+
+ for (i = 1; i < argc; i++)
+ {
+ FILE *f = fopen(argv[i], "rb");
+ long len;
+ uint8_t *buf;
+ size_t n_read;
+
+ if (!f)
+ {
+ fprintf(stderr, "%s: could not open %s: %m\n", argv[0], argv[i]);
+ ret = 1;
+ continue;
+ }
+
+ fseek(f, 0, SEEK_END);
+ len = ftell(f);
+ fseek(f, 0, SEEK_SET);
+
+ if (len < 0)
+ {
+ fprintf(stderr, "%s: could not determine size of %s\n",
+ argv[0], argv[i]);
+ fclose(f);
+ ret = 1;
+ continue;
+ }
+
+ buf = malloc(len);
+ if (!buf)
+ {
+ fprintf(stderr, "%s: out of memory\n", argv[0]);
+ fclose(f);
+ return 1;
+ }
+
+ n_read = fread(buf, 1, len, f);
+ fclose(f);
+
+ LLVMFuzzerTestOneInput(buf, n_read);
+ free(buf);
+ }
+
+ return ret;
+}
+#endif /* STANDALONE_FUZZ_TARGET */
--- /dev/null
+# Copyright (c) 2026, PostgreSQL Global Development Group
+
+# Fuzzing harnesses for security testing.
+#
+# Build with:
+# meson setup build -Dfuzzing=true
+#
+# For libFuzzer (recommended), also pass sanitizer flags:
+# meson setup build -Dfuzzing=true \
+# -Dc_args='-fsanitize=fuzzer-no-link' \
+# -Dc_link_args='-fsanitize=fuzzer'
+#
+# Without a fuzzer engine the harnesses are built in standalone mode:
+# each reads input from files named on the command line.
+
+if not get_option('fuzzing')
+ subdir_done()
+endif
+
+# Detect whether a fuzzer engine (e.g. libFuzzer) is available.
+# If so, link fuzzer executables with -fsanitize=fuzzer so that the
+# engine provides main(). Otherwise compile with STANDALONE_FUZZ_TARGET
+# so the harnesses supply their own main() that reads from files.
+
+fuzz_c_args = []
+fuzz_link_args = []
+
+if cc.has_argument('-fsanitize=fuzzer-no-link')
+ fuzzer_has_engine = cc.links('''
+ #include <stdint.h>
+ #include <stddef.h>
+ int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size)
+ { return 0; }
+ ''',
+ args: ['-fsanitize=fuzzer'],
+ name: 'libFuzzer support')
+else
+ fuzzer_has_engine = false
+endif
+
+if fuzzer_has_engine
+ fuzz_link_args += ['-fsanitize=fuzzer']
+else
+ fuzz_c_args += ['-DSTANDALONE_FUZZ_TARGET']
+endif
+
+# --- Frontend targets (no backend dependencies) ---
+
+fuzz_json = executable('fuzz_json',
+ 'fuzz_json.c',
+ c_args: fuzz_c_args,
+ link_args: fuzz_link_args,
+ dependencies: [frontend_code],
+ kwargs: default_bin_args + {
+ 'install': false,
+ },
+)
+
+fuzz_json_incremental = executable('fuzz_json_incremental',
+ 'fuzz_json_incremental.c',
+ c_args: fuzz_c_args,
+ link_args: fuzz_link_args,
+ dependencies: [frontend_code],
+ kwargs: default_bin_args + {
+ 'install': false,
+ },
+)
+
+fuzz_conninfo = executable('fuzz_conninfo',
+ 'fuzz_conninfo.c',
+ c_args: fuzz_c_args,
+ link_args: fuzz_link_args,
+ dependencies: [frontend_code, libpq],
+ kwargs: default_bin_args + {
+ 'install': false,
+ },
+)
+
+fuzz_pglz = executable('fuzz_pglz',
+ 'fuzz_pglz.c',
+ c_args: fuzz_c_args,
+ link_args: fuzz_link_args,
+ dependencies: [frontend_code],
+ kwargs: default_bin_args + {
+ 'install': false,
+ },
+)
+
+fuzz_unescapebytea = executable('fuzz_unescapebytea',
+ 'fuzz_unescapebytea.c',
+ c_args: fuzz_c_args,
+ link_args: fuzz_link_args,
+ dependencies: [frontend_code, libpq],
+ kwargs: default_bin_args + {
+ 'install': false,
+ },
+)
+
+fuzz_b64decode = executable('fuzz_b64decode',
+ 'fuzz_b64decode.c',
+ c_args: fuzz_c_args,
+ link_args: fuzz_link_args,
+ dependencies: [frontend_code],
+ kwargs: default_bin_args + {
+ 'install': false,
+ },
+)
+
+fuzz_saslprep = executable('fuzz_saslprep',
+ 'fuzz_saslprep.c',
+ c_args: fuzz_c_args,
+ link_args: fuzz_link_args,
+ dependencies: [frontend_code],
+ kwargs: default_bin_args + {
+ 'install': false,
+ },
+)
+
+fuzz_parsepgarray = executable('fuzz_parsepgarray',
+ 'fuzz_parsepgarray.c',
+ c_args: fuzz_c_args,
+ link_args: fuzz_link_args,
+ dependencies: [frontend_code, libpq],
+ kwargs: default_bin_args + {
+ 'install': false,
+ },
+)
+
+# The pgbench expression parser is built from generated Bison/Flex
+# sources. We reference the same custom_target outputs used by the
+# pgbench build, and provide our own syntax_error() / strtoint64() /
+# strtodouble() so we don't pull in pgbench.c (which has its own
+# main() and calls exit() on parse errors).
+
+exprscan_fuzz = custom_target('exprscan_fuzz',
+ input: files('../../bin/pgbench/exprscan.l'),
+ output: 'exprscan.c',
+ command: flex_cmd,
+)
+
+exprparse_fuzz = custom_target('exprparse_fuzz',
+ input: files('../../bin/pgbench/exprparse.y'),
+ kwargs: bison_kw,
+)
+
+fuzz_pgbench_expr = executable('fuzz_pgbench_expr',
+ 'fuzz_pgbench_expr.c',
+ exprscan_fuzz,
+ exprparse_fuzz,
+ c_args: fuzz_c_args,
+ link_args: fuzz_link_args,
+ include_directories: include_directories('../../bin/pgbench'),
+ dependencies: [frontend_code, libpq],
+ kwargs: default_bin_args + {
+ 'install': false,
+ },
+)
+
+# --- Backend targets ---
+#
+# These link against postgres_lib using standard archive semantics
+# (link_with), so only objects needed to resolve symbols are pulled in.
+# The harness provides stub definitions for symbols exported by
+# main/main.c, preventing the archive linker from pulling in main.o
+# (which would conflict with the harness's own main()).
+#
+# Backend code uses function-pointer casts in hash tables (dynahash.c)
+# that trigger UBSan's -fsanitize=function check. This is a known
+# benign pattern; when using -fsanitize=undefined, also pass
+# -fno-sanitize=function in the top-level c_args to suppress it.
+
+fuzz_rawparser = executable('fuzz_rawparser',
+ 'fuzz_rawparser.c',
+ c_args: fuzz_c_args,
+ link_args: fuzz_link_args,
+ link_with: [postgres_lib],
+ dependencies: backend_build_deps,
+ kwargs: default_bin_args + {
+ 'install': false,
+ },
+)
+
+fuzz_regex = executable('fuzz_regex',
+ 'fuzz_regex.c',
+ c_args: fuzz_c_args,
+ link_args: fuzz_link_args,
+ link_with: [postgres_lib],
+ dependencies: backend_build_deps,
+ kwargs: default_bin_args + {
+ 'install': false,
+ },
+)
+
+fuzz_typeinput = executable('fuzz_typeinput',
+ 'fuzz_typeinput.c',
+ c_args: fuzz_c_args,
+ link_args: fuzz_link_args,
+ link_with: [postgres_lib],
+ dependencies: backend_build_deps,
+ kwargs: default_bin_args + {
+ 'install': false,
+ },
+)
subdir('icu')
endif
+subdir('fuzzing')
+
subdir('perl')