/*
 * libfyaml-test-fuzzing.c - libfyaml fuzzing regression testing harness
 *
 * Copyright (c) 2019 Pantelis Antoniou <pantelis.antoniou@konsulko.com>
 *
 * SPDX-License-Identifier: MIT
 */

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <assert.h>

#include <check.h>

#include <libfyaml.h>

#include "fy-check.h"

/* Test: parse "*********&&&&&&" with RESOLVE_DOCUMENT | YPATH_ALIASES */
START_TEST(fuzz_resolve_aliases_stars_amps)
{
	char buf[] = "*********&&&&&&";
	struct fy_parse_cfg cfg = {0};
	cfg.flags = FYPCF_RESOLVE_DOCUMENT | FYPCF_YPATH_ALIASES;
	fy_document_destroy(fy_document_build_from_string(&cfg, buf, FY_NT));
}
END_TEST

/* Test: parse ":\n*.." with RESOLVE_DOCUMENT | DISABLE_BUFFERING | YPATH_ALIASES | ALLOW_DUPLICATE_KEYS */
START_TEST(fuzz_resolve_disable_buffering_colon_star)
{
	struct fy_parse_cfg cfg = {0};
	cfg.flags = FYPCF_RESOLVE_DOCUMENT | FYPCF_DISABLE_BUFFERING | FYPCF_YPATH_ALIASES | FYPCF_ALLOW_DUPLICATE_KEYS;
	fy_document_destroy(fy_document_build_from_string(&cfg, ":\n*..", FY_NT));
}
END_TEST

/* Test: parse special chars with RESOLVE_DOCUMENT | YPATH_ALIASES */
START_TEST(fuzz_resolve_aliases_special_chars)
{
	char buf[] = ": *$...!/*$///";
	struct fy_parse_cfg cfg = {0};
	cfg.flags = FYPCF_RESOLVE_DOCUMENT | FYPCF_YPATH_ALIASES;
	fy_document_destroy(fy_document_build_from_string(&cfg, buf, FY_NT));
}
END_TEST

/* Test: fy_emit_document_to_fp with NULL document */
START_TEST(fuzz_emit_null_document)
{
	fy_emit_document_to_fp(NULL, FYECF_EXTENDED_CFG, stdout);
}
END_TEST

#if defined(__linux__)

/* Test: fy_node_build_from_fp with invalid UTF-8 data */
START_TEST(fuzz_node_build_fp_invalid_data)
{
	struct fy_document *fyd = NULL;
	FILE *f = NULL;
	char data[] = "\x7b\x5b\xa8\x59\x3a";

	fyd = fy_document_create(NULL);
	ck_assert_ptr_ne(fyd, NULL);

	f = fmemopen((void *)data, sizeof(data), "r");
	ck_assert_ptr_ne(f, NULL);

	struct fy_node *fyn = fy_node_build_from_fp(fyd, f);
	fy_document_set_root(fyd, fyn);

	if (f) fclose(f);
	fy_document_destroy(fyd);
}
END_TEST

#endif

/* Test: parse sequence with anchors and aliases, recursive resolve */
START_TEST(fuzz_recursive_resolve_anchors_aliases)
{
	char buf[] = "\x2d\x0a\x2d\x20\x0d\x0a\x2d\x20\x26\x2d\x0a\x2d\x20\x0d\x0a\x2d\x20\x26\x2d\x0a\x20\x0d\x0a\x2d\x20\x26\x2d\x0a\x2d\x20\x2a\x2f\x37\x37\x37\x37\x37\x37\x37\x3e\x37\x37\x0a\x2d\x20\x26\x2d\x0a\x2d\x20\x2a\x2f\x39\x32\x36\x38\x30\x33\x3a\x32";
	struct fy_parse_cfg cfg = {0};
	cfg.flags = FYPCF_RESOLVE_DOCUMENT | FYPCF_PREFER_RECURSIVE | FYPCF_YPATH_ALIASES;
	fy_document_destroy(fy_document_build_from_string(&cfg, buf, FY_NT));
}
END_TEST

#if defined(__linux__)

/* Test: fy_node_build_from_fp with emoji and invalid UTF-8 via file */
START_TEST(fuzz_node_build_fp_emoji_invalid_utf8)
{
	unsigned char test_yaml[] = {
		'-', '-', '-', '\n',
		'-', '\n',
		' ', ' ', '\'', 'e', 'm', 'o', 'j', 'i', ' ',
		0xf0, 0x9f, 0x98, 0x80,
		'\'', ':', ' ', '{', 'w', 'z', 'h', ':', ' ', '[', '"', 't', 'a', 'b', '\t', 's', 'e', 'p', '"', ',', ' ',
		'\'', 'p', 'l', 'a', 'i', 'n', ' ', 's', 'c', 'a', 'l', 'a', 'r', '\'', ',', ' ',
		0x92,
		'd', 't', 'y', ']', '}', '\n',
		0
	};

	FILE *fp = fmemopen((void *)test_yaml, sizeof(test_yaml) - 1, "r");
	ck_assert_ptr_ne(fp, NULL);

	struct fy_document *doc = fy_document_create(NULL);
	ck_assert_ptr_ne(doc, NULL);

	struct fy_node *n = fy_node_build_from_fp(doc, fp);
	fclose(fp);

	if (n)
		fy_node_free(n);

	fy_document_destroy(doc);
}
END_TEST

#endif

/* Test: emit event with invalid scalar style */
START_TEST(fuzz_emit_event_invalid_scalar_style)
{
	struct fy_emitter_cfg cfg = {0};
	cfg.flags = (enum fy_emitter_cfg_flags)0;
	cfg.output = NULL;
	cfg.userdata = NULL;
	cfg.diag = NULL;

	struct fy_emitter *emit = fy_emitter_create(&cfg);
	ck_assert_ptr_ne(emit, NULL);

	struct fy_event *ev = fy_emit_event_create(emit, FYET_STREAM_START);
	ck_assert_ptr_ne(ev, NULL);
	ck_assert_int_eq(fy_emit_event(emit, ev), 0);

	ev = fy_emit_event_create(emit, FYET_DOCUMENT_START, 1, NULL, NULL);
	ck_assert_ptr_ne(ev, NULL);
	ck_assert_int_eq(fy_emit_event(emit, ev), 0);

	enum fy_scalar_style invalid_style = (enum fy_scalar_style)(-2);
	ev = fy_emit_event_create(emit, FYET_SCALAR, invalid_style, "test", (size_t)4, NULL, NULL);
	/* should return NULL for invalid style, not crash */
	(void)ev;
	if (ev)
		fy_emit_event_free(emit, ev);

	fy_emitter_destroy(emit);
}
END_TEST

/* Test: parse binary-like data with recursive resolve and ypath aliases */
START_TEST(fuzz_recursive_resolve_binary_data)
{
	char buf[] = "\x2f\x20\x20\x2d\x2f\x2f\x2a\x2f\x65\x2f\x2f\x2f\x31\x26\x26\x2f\x20\x20\x3a\x0a\x0a\x2a\x2f\x2f\x2e\x2f\x20\xd7\xd0\xd0\xd0\xd0\x19\x3a\x0d\x30\x2e\x3a\x0d\x35\x7a\x3a\x68\x3a\x0d\x04\x26\x18\x3a\x0d\x32\x2e\x2d\x0a\x20\x26\x2d\x32\x26\x2a\x2a\x2d\x0a\x0a\x2d\x0a\x2d\x2a\x20\x5f\x2d\x0a\x2d\x0a\x2d\x20\xf6\xdf\xd2\xdf\xcd\xd9\xd5\xf9\x2d\x0a\x2d\x20\x2d\x20";
	struct fy_parse_cfg cfg = {0};
	cfg.flags = FYPCF_RESOLVE_DOCUMENT | FYPCF_PREFER_RECURSIVE | FYPCF_YPATH_ALIASES;
	fy_document_destroy(fy_document_build_from_string(&cfg, buf, FY_NT));
}
END_TEST

/* Test: fy_node_by_path with YPATH on sequence */
START_TEST(fuzz_node_by_path_ypath_sequence)
{
	char buf[] = "\x40\x61\x3e\x58\x40";
	struct fy_document *fyd = NULL;

	fyd = fy_document_create(NULL);
	ck_assert_ptr_ne(fyd, NULL);

	struct fy_node *fyn = fy_node_create_sequence(fyd);
	fy_document_set_root(fyd, fyn);
	struct fy_node *root = fy_document_root(fyd);
	struct fy_node *node = fy_node_by_path(root, buf, FY_NT, FYNWF_PTR_YPATH);
	(void)node;

	fy_document_destroy(fyd);
}
END_TEST

/* Test: fy_token_iter_getc after fy_token_iter_read */
START_TEST(fuzz_token_iter_getc_after_read)
{
	struct fy_document *fyd = NULL;
	struct fy_parse_cfg cfg = {0};
	struct fy_token_iter *iter = NULL;
	char buf[] = "!n2_";

	fyd = fy_document_build_from_string(&cfg, buf, FY_NT);
	if (!fyd)
		return;

	struct fy_node *root = fy_document_root(fyd);
	if (!root || !fy_node_is_scalar(root))
		goto out;

	struct fy_token *token = fy_node_get_scalar_token(root);
	if (!token)
		goto out;

	iter = fy_token_iter_create(token);
	if (!iter)
		goto out;

	char buf2[256];
	fy_token_iter_read(iter, buf2, sizeof(buf2) - 1);

	int c = fy_token_iter_getc(iter);
	(void)c;

out:
	fy_token_iter_destroy(iter);
	fy_document_destroy(fyd);
}
END_TEST

/* Test: parse ":\n*.." with COLLECT_DIAG and many flags */
START_TEST(fuzz_resolve_collect_diag_colon_star)
{
	char buf[] = ":\n*..";
	struct fy_parse_cfg cfg = {0};
	cfg.flags = FYPCF_RESOLVE_DOCUMENT | FYPCF_DISABLE_RECYCLING | FYPCF_DISABLE_BUFFERING | FYPCF_YPATH_ALIASES | FYPCF_ALLOW_DUPLICATE_KEYS;
	fy_document_destroy(fy_document_build_from_string(&cfg, buf, FY_NT));
}
END_TEST

/* Test: parse ":\n*.." with COLLECT_DIAG flag included */
START_TEST(fuzz_collect_diag_colon_star)
{
	char buf[] = ":\n*..";
	struct fy_parse_cfg cfg = {0};
	cfg.flags = FYPCF_COLLECT_DIAG | FYPCF_RESOLVE_DOCUMENT | FYPCF_DISABLE_RECYCLING | FYPCF_DISABLE_BUFFERING | FYPCF_YPATH_ALIASES | FYPCF_ALLOW_DUPLICATE_KEYS;
	fy_document_destroy(fy_document_build_from_string(&cfg, buf, FY_NT));
}
END_TEST

/* Test: emit document with STRIP_EMPTY_KV and many mode flags */
START_TEST(fuzz_emit_strip_empty_kv_many_modes)
{
	struct fy_document *fyd = NULL;
	struct fy_parse_cfg cfg = {0};
	char *buf = NULL;

	fyd = fy_document_build_from_string(&cfg, ":\n*$@", FY_NT);
	if (!fyd)
		goto out;

	buf = fy_emit_document_to_string(fyd, FYECF_STRIP_EMPTY_KV | FYECF_MODE_BLOCK | FYECF_MODE_FLOW | FYECF_MODE_FLOW_ONELINE | FYECF_MODE_JSON | FYECF_MODE_JSON_TP | FYECF_MODE_JSON_ONELINE | FYECF_MODE_DEJSON | FYECF_MODE_PRETTY | FYECF_MODE_MANUAL | FYECF_MODE_FLOW_COMPACT | FYECF_MODE_JSON_COMPACT | FYECF_DOC_START_MARK_OFF | FYECF_VERSION_DIR_ON);

out:
	free(buf);
	fy_document_destroy(fyd);
}
END_TEST

/* Test: fy_path_expr_build_from_string with "!***" */
START_TEST(fuzz_path_expr_triple_star)
{
	struct fy_path_parse_cfg parse_cfg = {0};
	struct fy_path_expr *expr = fy_path_expr_build_from_string(&parse_cfg, "!***", FY_NT);
	fy_path_expr_free(expr);
}
END_TEST

/* Test: parse star-slash-bang with RESOLVE_DOCUMENT | PREFER_RECURSIVE | YPATH_ALIASES | ALLOW_DUPLICATE_KEYS */
START_TEST(fuzz_resolve_recursive_star_slash_bang)
{
	struct fy_parse_cfg cfg = {0};
	cfg.flags = FYPCF_RESOLVE_DOCUMENT | FYPCF_PREFER_RECURSIVE | FYPCF_YPATH_ALIASES | FYPCF_ALLOW_DUPLICATE_KEYS;
	fy_document_destroy(fy_document_build_from_string(&cfg, "*//!!", FY_NT));
}
END_TEST

/* Test: fy_node_by_path with "*@" via JSON/RELJSON/YPATH flags */
START_TEST(fuzz_node_by_path_star_at)
{
	struct fy_document *fyd = NULL;
	struct fy_parse_cfg cfg = {0};

	cfg.flags = FYPCF_YPATH_ALIASES;

	fyd = fy_document_build_from_string(&cfg, ":", FY_NT);
	if (!fyd)
		return;

	struct fy_node *root = fy_document_root(fyd);
	struct fy_node *node = fy_node_by_path(root, "*@", FY_NT, FYNWF_PTR_JSON | FYNWF_PTR_RELJSON | FYNWF_PTR_YPATH);
	(void)node;

	fy_document_destroy(fyd);
}
END_TEST

/* Test: fy_node_by_path with "**@" and emit to JSON */
START_TEST(fuzz_node_by_path_double_star_at_emit)
{
	struct fy_document *fyd = NULL;
	struct fy_parse_cfg cfg = {0};
	cfg.flags = FYPCF_PREFER_RECURSIVE | FYPCF_JSON_NONE;

	fyd = fy_document_build_from_string(&cfg, ":", FY_NT);
	if (!fyd)
		return;

	struct fy_node *root = fy_document_root(fyd);
	struct fy_node *node = fy_node_by_path(root, "**@", FY_NT, FYNWF_PTR_JSON | FYNWF_PTR_RELJSON | FYNWF_PTR_YPATH);
	char *b = fy_emit_node_to_string(node, FYECF_STRIP_LABELS | FYECF_MODE_JSON | FYECF_MODE_JSON_TP | FYECF_MODE_JSON_ONELINE | FYECF_MODE_DEJSON);

	fy_document_destroy(fyd);
	free(b);
}
END_TEST

/* Test: fy_node_by_path with "*_Y" on empty sequence */
START_TEST(fuzz_node_by_path_star_underscore_sequence)
{
	struct fy_document *fyd = NULL;

	fyd = fy_document_create(NULL);
	ck_assert_ptr_ne(fyd, NULL);

	struct fy_node *fyn = fy_node_create_sequence(fyd);
	fy_document_set_root(fyd, fyn);
	struct fy_node *root = fy_document_root(fyd);
	struct fy_node *node = fy_node_by_path(root, "*_Y", FY_NT, FYNWF_FOLLOW | FYNWF_PTR_JSON | FYNWF_PTR_RELJSON | FYNWF_PTR_YPATH);
	(void)node;

	fy_document_destroy(fyd);
}
END_TEST

/* Test: parse with many ypath alias patterns, recursive resolve + duplicate keys */
START_TEST(fuzz_ypath_aliases_complex_pattern)
{
	char buf[] = "\x2a\x2a\x40\x28\x28\x29\x30\x30\x28\x29\x30\x2a\x28\x2d\x2a\x2a\x40\x28\x28\x2a\x29\x2a\x30\x40\x40\x2a\x28\x30\x28\x28\x40\x29\x2d\x30\x29\x2d\x40\x30\x37\x29\x40\x40\x30\x28\x29\x30\x28\x28\x2a\x29\x2a\x30\x40\x40\x2a\x28\x30\x28\x28\x40\x29\x2d\x30\x29\x2d\x40\x30\x37\x29\x40\x40\x30\x28\x29\x30\x28\x71\x71\x71\x2d\x40\x40\x2a\x2a\x33\x40\x2a\x2a\x30\x2a\x40\x2a\x2d\x2a\x2a\x30\x30\x00\x00\x00\x00";
	struct fy_parse_cfg cfg = {0};
	cfg.flags = FYPCF_RESOLVE_DOCUMENT | FYPCF_YPATH_ALIASES | FYPCF_ALLOW_DUPLICATE_KEYS;
	fy_document_destroy(fy_document_build_from_string(&cfg, buf, FY_NT));
}
END_TEST

/* Test: parse with sloppy flow indentation and many disable flags */
START_TEST(fuzz_sloppy_flow_disable_flags)
{
	char buf[] = "\x20\x2d\x20\x3f\x20\x20\x3a\x20\x20\x2a\x2a\x24\x2e\x2e\x2a\x2a\x2a\x2f\x2f\x2f\x24\x2e\x2e\x2e\x2a\x25\x2f\x2a\x2a\x2f\x2f\x40\x2e\x2a\x24\x24\x2a\x2a\x2e\x2e\x2a\x2a\x2f\x2f\x2f\x2a\x2a\x2f\x6c\x2a\x2f\x2f\x2f\x2f\x2f\x2f\x2f\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00";
	struct fy_parse_cfg cfg = {0};
	cfg.flags = FYPCF_RESOLVE_DOCUMENT | FYPCF_DISABLE_MMAP_OPT | FYPCF_DISABLE_RECYCLING | FYPCF_DISABLE_ACCELERATORS | FYPCF_SLOPPY_FLOW_INDENTATION | FYPCF_YPATH_ALIASES;
	fy_document_destroy(fy_document_build_from_string(&cfg, buf, FY_NT));
}
END_TEST

/* Test: parse with disable recycling and ypath aliases */
START_TEST(fuzz_disable_recycling_ypath_aliases)
{
	char buf[] = "\x2d\x0a\x20\x3f\x2c\x20\x20\x2d\x20\x2a\x60\x24\x2e\x2d\x0a\x2d\x20\x2a\x2f\x2f\x2e\x30\x40\x24\x2f\x21\x2f\x2f\x78\x2f\x2f\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00";
	struct fy_parse_cfg cfg = {0};
	cfg.flags = FYPCF_RESOLVE_DOCUMENT | FYPCF_DISABLE_RECYCLING | FYPCF_YPATH_ALIASES;
	fy_document_destroy(fy_document_build_from_string(&cfg, buf, FY_NT));
}
END_TEST

#if defined(__linux__)

/* Test: fy_document_build_from_fp with sloppy flow indentation */
START_TEST(fuzz_build_from_fp_sloppy_flow)
{
	struct fy_document *fyd = NULL;
	struct fy_parse_cfg cfg = {0};
	FILE *f = NULL;

	cfg.flags = FYPCF_RESOLVE_DOCUMENT | FYPCF_SLOPPY_FLOW_INDENTATION | FYPCF_ALLOW_DUPLICATE_KEYS;

	char data[] = "-\n*/-";
	f = fmemopen((void *)data, strlen(data), "r");
	if (!f)
		return;

	fyd = fy_document_build_from_fp(&cfg, f);

	if (f) fclose(f);
	fy_document_destroy(fyd);
}
END_TEST

/* Test: fy_node_build_from_fp with "[\n:]" */
START_TEST(fuzz_node_build_fp_flow_mapping)
{
	char buf[] = "\x5b\x0a\x3a\x5d\x00";
	struct fy_document *fyd = NULL;
	FILE *f = NULL;

	fyd = fy_document_create(NULL);
	if (!fyd)
		return;

	f = fmemopen((void *)buf, sizeof(buf), "r");
	if (!f)
		goto out;

	struct fy_node *fyn = fy_node_build_from_fp(fyd, f);
	if (fyn)
		fy_document_set_root(fyd, fyn);

out:
	if (f) fclose(f);
	fy_document_destroy(fyd);
}
END_TEST

#endif	// __linux__

/* Test: parse complex anchors/aliases with disable buffering and recursive */
START_TEST(fuzz_complex_anchors_recursive_buffering)
{
	char buf[] = "\x2d\x20\x3f\x20\x2d\x20\x2a\x2d\x0a\x23\x0a\x2d\x20\x0d\x0a\x20\x20\x3f\x20\x2d\x20\x2d\x20\x2a\x2d\x0d\x0a\x2d\x20\x2a\x2d\x0a\x2d\x0a\x2d\x20\x20\x2a\x2d\x0a\x23\x0a\x2d\x20\x0d\x0a\x20\x20\x3f\x20\x2d\x20\x3f\x20\x2d\x20\x2a\x2d\x0a\x23\x0a\x2d\x20\x0d\x0a\x20\x20\x3f\x20\x2d\x20\x2d\x20\x2a\x2d\x0a\x2d\x20\x26\x2d\x20\x0a\x20\x2d\x20\x20\x20\x3f\x20\x2d\x20\x3f\x20\x2a\x2f\x2f\x2a\x2a\x40\x00\x00\x00\x00";
	struct fy_parse_cfg cfg = {0};
	cfg.flags = FYPCF_RESOLVE_DOCUMENT | FYPCF_DISABLE_BUFFERING | FYPCF_SLOPPY_FLOW_INDENTATION | FYPCF_PREFER_RECURSIVE | FYPCF_YPATH_ALIASES;

	struct fy_document *fyd = fy_document_build_from_string(&cfg, buf, FY_NT);
	fy_document_destroy(fyd);
}
END_TEST

/* Test: fy_node_build_from_string with " >\n%" */
START_TEST(fuzz_node_build_string_block_scalar)
{
	struct fy_document *fyd = NULL;

	fyd = fy_document_create(NULL);
	if (!fyd)
		return;

	struct fy_node *fyn = fy_node_build_from_string(fyd, " >\n%\x00", FY_NT);
	if (fyn)
		fy_document_set_root(fyd, fyn);

	fy_document_destroy(fyd);
}
END_TEST

/* Test: fy_node_by_path with "./" and emit with many mode flags */
START_TEST(fuzz_node_by_path_dot_slash_emit)
{
	struct fy_document *fyd = NULL;
	struct fy_parse_cfg cfg = {0};
	char *buf = NULL;

	cfg.flags = FYPCF_COLLECT_DIAG | FYPCF_DISABLE_MMAP_OPT | FYPCF_DISABLE_RECYCLING | FYPCF_DISABLE_BUFFERING;
	int flags2 = FYNWF_PTR_JSON | FYNWF_PTR_RELJSON | FYNWF_PTR_YPATH | FYNWF_URI_ENCODED;
	int flags3 = FYECF_MODE_BLOCK | FYECF_MODE_FLOW | FYECF_MODE_FLOW_ONELINE | FYECF_MODE_JSON_TP | FYECF_MODE_JSON_ONELINE | FYECF_MODE_DEJSON | FYECF_MODE_PRETTY | FYECF_MODE_MANUAL | FYECF_MODE_FLOW_COMPACT | FYECF_MODE_JSON_COMPACT;

	fyd = fy_document_build_from_string(&cfg, "**", FY_NT);
	if (!fyd)
		goto out;

	struct fy_node *root = fy_document_root(fyd);
	struct fy_node *node = fy_node_by_path(root, "./", FY_NT, flags2);

	if (!node)
		goto out;

	buf = fy_emit_node_to_string(node, flags3);

out:
	fy_document_destroy(fyd);
	free(buf);
}
END_TEST

/* Test: fy_node_by_path with "(1()" on sequence */
START_TEST(fuzz_node_by_path_parens_sequence)
{
	char buf[] = "(1()";
	struct fy_document *fyd = NULL;

	fyd = fy_document_create(NULL);
	if (!fyd)
		return;

	struct fy_node *fyn = fy_node_create_sequence(fyd);
	fy_document_set_root(fyd, fyn);
	struct fy_node *root = fy_document_root(fyd);
	struct fy_node *node = fy_node_by_path(root, buf, FY_NT, FYNWF_PTR_JSON | FYNWF_PTR_RELJSON | FYNWF_PTR_YPATH | FYNWF_URI_ENCODED);
	(void)node;

	fy_document_destroy(fyd);
}
END_TEST

/* Test: parse comment-heavy input with PARSE_COMMENTS | PREFER_RECURSIVE and emit with many flags */
START_TEST(fuzz_parse_comments_recursive_emit)
{
	char buf[] = "\x23\x63\x3a\x0d\x0a\x23\x3a\x0a\x23\x24\x0d\x01\x7c\x23\x3a\x09\x52\x25\x42";
	struct fy_parse_cfg cfg = {0};
	struct fy_document *fyd = NULL;
	FILE *fp = NULL;

	cfg.flags = FYPCF_PARSE_COMMENTS | FYPCF_DISABLE_ACCELERATORS | FYPCF_PREFER_RECURSIVE;

	fyd = fy_document_build_from_string(&cfg, buf, FY_NT);
	if (!fyd)
		return;

	fp = fopen("/dev/null", "w");
	if (!fp)
		goto out;

	fy_emit_document_to_fp(fyd, FYECF_STRIP_DOC | FYECF_NO_ENDING_NEWLINE | FYECF_MODE_BLOCK | FYECF_MODE_FLOW_ONELINE | FYECF_MODE_JSON | FYECF_MODE_JSON_TP | FYECF_MODE_JSON_ONELINE | FYECF_MODE_DEJSON | FYECF_MODE_MANUAL | FYECF_MODE_JSON_COMPACT, fp);

out:
	if (fp) fclose(fp);
	fy_document_destroy(fyd);
}
END_TEST

#ifdef __linux__

/* Test: fy_document_build_from_fp with ":\r:" and recursive resolve + duplicate keys */
START_TEST(fuzz_build_from_fp_recursive_duplicate_keys)
{
	char buf[] = "\x3a\x0d\x3a";
	struct fy_document *fyd = NULL;
	struct fy_parse_cfg cfg = {0};
	FILE *f = NULL;

	cfg.flags = FYPCF_RESOLVE_DOCUMENT | FYPCF_DISABLE_MMAP_OPT | FYPCF_DISABLE_ACCELERATORS | FYPCF_PREFER_RECURSIVE | FYPCF_ALLOW_DUPLICATE_KEYS;

	f = fmemopen((void *)buf, strlen(buf), "r");
	if (!f)
		return;

	fyd = fy_document_build_from_fp(&cfg, f);

	if (f) fclose(f);
	fy_document_destroy(fyd);
}
END_TEST

#endif

#if defined(__linux__)

static void fuzz_dump_testsuite_event(struct fy_parser *fyp, struct fy_event *fye)
{
	const char *anchor = NULL;
	const char *tag = NULL;
	const char *text = NULL;
	const char *alias = NULL;
	size_t anchor_len = 0, tag_len = 0, text_len = 0, alias_len = 0;
	const struct fy_mark *sm, *em = NULL;

	sm = fy_event_start_mark(fye);
	em = fy_event_end_mark(fye);
	(void)sm; (void)em;

	switch (fye->type) {
	case FYET_NONE:
	case FYET_STREAM_START:
	case FYET_STREAM_END:
	case FYET_DOCUMENT_START:
	case FYET_DOCUMENT_END:
	case FYET_MAPPING_END:
	case FYET_SEQUENCE_END:
	case FYET_ALIAS:
		break;
	case FYET_MAPPING_START:
		if (fye->mapping_start.anchor)
			anchor = fy_token_get_text(fye->mapping_start.anchor, &anchor_len);
		if (fye->mapping_start.tag)
			tag = fy_token_get_text(fye->mapping_start.tag, &tag_len);
		break;
	case FYET_SEQUENCE_START:
		if (fye->sequence_start.anchor)
			anchor = fy_token_get_text(fye->sequence_start.anchor, &anchor_len);
		if (fye->sequence_start.tag)
			tag = fy_token_get_text(fye->sequence_start.tag, &tag_len);
		break;
	case FYET_SCALAR:
		if (fye->scalar.anchor)
			anchor = fy_token_get_text(fye->scalar.anchor, &anchor_len);
		if (fye->scalar.tag)
			tag = fy_token_get_text(fye->scalar.tag, &tag_len);
		break;
	default:
		break;
	}

	switch (fye->type) {
	default:
		break;
	case FYET_SCALAR:
		text = fy_token_get_text(fye->scalar.value, &text_len);
		break;
	case FYET_ALIAS:
		alias = fy_token_get_text(fye->alias.anchor, &alias_len);
		break;
	}

	(void)anchor; (void)anchor_len;
	(void)tag; (void)tag_len;
	(void)text; (void)text_len;
	(void)alias; (void)alias_len;
}

/* Test: parse ">\x00\x09\x0d" via fy_parser_parse event loop */
START_TEST(fuzz_parser_event_loop_block_scalar)
{
	char buf[] = "\x3e\x00\x09\x0d";
	struct fy_parser *fyp = NULL;
	struct fy_parse_cfg cfg = {0};
	struct fy_event *fyev = NULL;
	FILE *f = NULL;

	f = fmemopen((void *)buf, 4, "r");
	if (!f)
		return;

	fyp = fy_parser_create(&cfg);
	if (!fyp)
		goto out;

	if (fy_parser_set_input_fp(fyp, NULL, f) != 0)
		goto out;

	while ((fyev = fy_parser_parse(fyp)) != NULL) {
		fuzz_dump_testsuite_event(fyp, fyev);
		fy_parser_event_free(fyp, fyev);
	}

out:
	if (f) fclose(f);
	fy_parser_destroy(fyp);
}
END_TEST

#endif

/* Test: parse sequence with embedded comments using COLLECT_DIAG | DISABLE_RECYCLING | PARSE_COMMENTS | DISABLE_BUFFERING */
START_TEST(fuzz_collect_diag_parse_comments_sequence)
{
	char data[] = "- foo\n#\n\n#\n- G";
	struct fy_parse_cfg cfg = {0};
	struct fy_document *fyd = NULL;

	cfg.flags = FYPCF_COLLECT_DIAG | FYPCF_DISABLE_RECYCLING | FYPCF_PARSE_COMMENTS | FYPCF_DISABLE_BUFFERING | FYPCF_JSON_NONE;

	fyd = fy_document_build_from_string(&cfg, data, FY_NT);
	fy_document_destroy(fyd);
}
END_TEST

/* Test: parse quoted scalar, clone document, and compare nodes via multiple compare APIs */
START_TEST(fuzz_node_compare_clone_quoted_scalar)
{
	char data[] = "'''''''''''' ";
	struct fy_parse_cfg cfg = {0};
	struct fy_document *fyd = NULL;
	struct fy_document *fyd2 = NULL;
	struct fy_node *root, *root2;
	bool same, equal, matches, text_matches;

	cfg.flags = FYPCF_PREFER_RECURSIVE | FYPCF_YPATH_ALIASES | FYPCF_ALLOW_DUPLICATE_KEYS;

	fyd = fy_document_build_from_string(&cfg, data, FY_NT);
	if (!fyd)
		return;

	root = fy_document_root(fyd);
	if (!root)
		goto out;

	same = fy_node_compare(root, root);
	(void)same;

	fyd2 = fy_document_clone(fyd);
	if (!fyd2)
		goto out;

	root2 = fy_document_root(fyd2);
	equal = fy_node_compare(root, root2);
	matches = fy_node_compare_string(root, data, FY_NT);
	text_matches = fy_node_compare_text(root, data, FY_NT);
	(void)equal; (void)matches; (void)text_matches;

out:
	fy_document_destroy(fyd);
	fy_document_destroy(fyd2);
}
END_TEST

/* Test: parse comment with override */
START_TEST(fuzz_parse_comment_with_override)
{
	char buf[] = "- a: b\n  # end\n# bottom\n";
	struct fy_parse_cfg cfg = {0};
	struct fy_document *fyd;

	cfg.flags = FYPCF_PARSE_COMMENTS | FYPCF_DISABLE_ACCELERATORS | FYPCF_PREFER_RECURSIVE;

	fyd = fy_document_build_from_string(&cfg, buf, FY_NT);
	fy_document_destroy(fyd);
}
END_TEST

START_TEST(fuzz_resolve_document_ypath_null_alias)
{
	char buf[] = ":\n*.null";
	struct fy_parse_cfg cfg = {0};
	struct fy_document *fyd;

	cfg.flags = FYPCF_RESOLVE_DOCUMENT | FYPCF_YPATH_ALIASES;

	fyd = fy_document_build_from_string(&cfg, buf, FY_NT);
	fy_document_destroy(fyd);
}
END_TEST

#if defined(__linux__)
START_TEST(fuzz_build_from_fp_ypath_aliases_recursive)
{
	char buf[] = "\n? - :\n? - : - */**";
	struct fy_parse_cfg cfg = {0};
	struct fy_document *fyd;
	FILE *f;

	cfg.flags = FYPCF_RESOLVE_DOCUMENT | FYPCF_PREFER_RECURSIVE | FYPCF_YPATH_ALIASES;

	f = fmemopen((void *)buf, strlen(buf), "r");
	fyd = fy_document_build_from_fp(&cfg, f);
	if (f)
		fclose(f);
	fy_document_destroy(fyd);
}
END_TEST
#endif

START_TEST(fuzz_path_expr_build_bang_triple_star)
{
	struct fy_path_parse_cfg parse_cfg = {0};
	struct fy_path_expr *expr;

	expr = fy_path_expr_build_from_string(&parse_cfg, "!***", FY_NT);
	fy_path_expr_free(expr);
}
END_TEST

START_TEST(fuzz_resolve_recursive_ypath_aliases_dup_keys)
{
	char buf[] = "*//!!";
	struct fy_parse_cfg cfg = {0};
	struct fy_document *fyd;

	cfg.flags = FYPCF_RESOLVE_DOCUMENT | FYPCF_PREFER_RECURSIVE |
		    FYPCF_YPATH_ALIASES | FYPCF_ALLOW_DUPLICATE_KEYS;

	fyd = fy_document_build_from_string(&cfg, buf, FY_NT);
	fy_document_destroy(fyd);
}
END_TEST

START_TEST(fuzz_disable_recycling_ypath_aliases_dup_keys)
{
	char buf[] = "*((0)/*";
	struct fy_parse_cfg cfg = {0};
	struct fy_document *fyd;

	cfg.flags = FYPCF_RESOLVE_DOCUMENT | FYPCF_DISABLE_RECYCLING |
		    FYPCF_YPATH_ALIASES | FYPCF_ALLOW_DUPLICATE_KEYS;

	fyd = fy_document_build_from_string(&cfg, buf, FY_NT);
	fy_document_destroy(fyd);
}
END_TEST

#if defined(__linux__)
START_TEST(fuzz_build_from_fp_sloppy_recursive_ypath_aliases)
{
	char data[] = "\x2a\x27\x2f\x2a\x27\x27\x24\x09\x09\x3a\x0a\x72\x3a\x0a\x2a\x2f\x72\x2f";
	struct fy_parse_cfg cfg = {0};
	struct fy_document *fyd;
	FILE *f;

	cfg.flags = FYPCF_RESOLVE_DOCUMENT | FYPCF_DISABLE_ACCELERATORS |
		    FYPCF_SLOPPY_FLOW_INDENTATION | FYPCF_PREFER_RECURSIVE |
		    FYPCF_YPATH_ALIASES;

	f = fmemopen((void *)data, strlen(data), "r");
	fyd = fy_document_build_from_fp(&cfg, f);
	if (f)
		fclose(f);
	fy_document_destroy(fyd);
}
END_TEST
#endif

START_TEST(fuzz_parse_comments_emit_many_modes)
{
	char data[] =
		"\x3a\x3a\x20\x3a\x20\x3a\x3a\x20\x3a\x20\x3f\x01\x05\x3a\x20\x3a\x20"
		"\x3f\x20\x3a\x3a\x20\x3a\x20\x3a\x20\x3a\x3a\x20\x3a\x20\x3f\x3a\x20"
		"\x3a\x20\x3b\x20\x3f\x01\x05\x3a\x20\x3a\x20\x3f\x20\x3a\x3a\x20\x3a"
		"\x20\x3a\x20\x3a\x3a\x20\x3a\x20\x3f\x3a\x20\x3a\x20\x3b\x20\x3f\x01"
		"\x05\x3a\x20\x3a\x20\x3f\x20\x3a\x3a\x20\x3a\x20\x3a\x20\x3a\x3a\x20"
		"\x3a\x20\x3f\x3a\x20\x3a\x20\x3b\x20\x3f\x20\x3a\x3f\x01\x05\x3a\x20"
		"\x3a\x20\x3f\x20\x3a\x3a\x20\x3a\x20\x3a\x20\x3a\x3a\x20\x3a\x20\x3a"
		"\x3a\x20\x3a\x20\x3f\x3a\x20\x3a\x20\x3b\x20\x3f\x20\x3a\x3a\x55\x55"
		"\x55\x55\x20\x3a\x3a\x20\x3a\x20\x3a\x20\x3f\x20\x3a\x3a\x20\x3a\x20"
		"\x3a\x20\x3a\x3a\x20\x3a\x20\x3f\x3a\x20\x3a\x20\x3b\x20\x3f\x20\x3a"
		"\x3f\x01\x05\x3a\x20\x3a\x20\x3f\x20\x3a\x3f\x20\x3a\x3f\x01\x05\x20"
		"\x3a\x3a\x20\x20\x3a\x20\x3f";
	struct fy_parse_cfg cfg = {0};
	struct fy_document *fyd;
	struct fy_emitter *emitter;
	char *collected;
	char buf[4096];
	int eflags;
	int rc;

	cfg.flags = FYPCF_DISABLE_RECYCLING | FYPCF_PARSE_COMMENTS;
	eflags = FYECF_OUTPUT_COMMENTS |
		 FYECF_WIDTH_DEFAULT | FYECF_WIDTH_80 | FYECF_WIDTH_132 | FYECF_WIDTH_INF |
		 FYECF_MODE_BLOCK | FYECF_MODE_FLOW | FYECF_MODE_FLOW_ONELINE |
		 FYECF_MODE_JSON | FYECF_MODE_JSON_TP | FYECF_MODE_JSON_ONELINE |
		 FYECF_MODE_DEJSON | FYECF_MODE_PRETTY | FYECF_MODE_MANUAL |
		 FYECF_MODE_FLOW_COMPACT | FYECF_MODE_JSON_COMPACT |
		 FYECF_TAG_DIR_OFF | FYECF_TAG_DIR_ON;

	fyd = fy_document_build_from_string(&cfg, data, FY_NT);
	if (!fyd)
		return;

	memset(buf, 0, sizeof(buf));
	rc = fy_emit_document_to_buffer(fyd, eflags, buf, sizeof(buf));
	(void)rc;

	emitter = fy_emit_to_string(eflags);
	if (emitter) {
		fy_emit_document(emitter, fyd);
		size_t out_size;
		collected = fy_emit_to_string_collect(emitter, &out_size);
		free(collected);
	}
	fy_emitter_destroy(emitter);
	fy_document_destroy(fyd);
}
END_TEST

START_TEST(fuzz_resolve_recursive_ypath_dup_keys_emit_fp)
{
	char data[] = "\x0a\x2a\x2f\x2a\x2f\x5e\x2f\x2a\x2f\x20\x09\x09\x3a\x0a\x72\x3a"
		      "\x0a\x2a\x5e\x2f\x2a\x2f\x20\x09\x09\x3a\x0a\x2a\x2f\x5e";
	struct fy_parse_cfg cfg = {0};
	struct fy_document *fyd;
	FILE *fp;
	int rc;

	cfg.flags = FYPCF_RESOLVE_DOCUMENT | FYPCF_PREFER_RECURSIVE |
		    FYPCF_YPATH_ALIASES | FYPCF_ALLOW_DUPLICATE_KEYS;

	fyd = fy_document_build_from_string(&cfg, data, FY_NT);
	if (!fyd)
		return;

	fp = fopen("/dev/null", "w");
	rc = fy_emit_document_to_fp(fyd,
		FYECF_MODE_BLOCK | FYECF_MODE_FLOW_ONELINE | FYECF_MODE_JSON |
		FYECF_MODE_JSON_TP | FYECF_MODE_JSON_ONELINE | FYECF_MODE_DEJSON |
		FYECF_MODE_MANUAL | FYECF_MODE_JSON_COMPACT,
		fp);
	(void)rc;
	if (fp)
		fclose(fp);
	fy_document_destroy(fyd);
}
END_TEST

START_TEST(fuzz_node_get_type_uaf)
{
	char data[] =
		"\x0a\x20\x2d\x20\x2a\x2f\x2e\x2e\x2a\x0a"
		"\x20\x2d\x20\x2a\x2f\x2a\x2a\x21\x0a"
		"\x20\x2d\x20\x2a\x2f\x2a\x2a\x2f";

	struct fy_parse_cfg cfg = {0};
	struct fy_document *fyd = NULL;

	cfg.flags = FYPCF_RESOLVE_DOCUMENT | FYPCF_PREFER_RECURSIVE |
		    FYPCF_YPATH_ALIASES;

	// this must fail
	fyd = fy_document_build_from_string(&cfg, data, FY_NT);
	ck_assert_ptr_eq(fyd, NULL);
}
END_TEST

START_TEST(fuzz_emit_node_to_string_uaf)
{
	char buf[] =
		"\x2a\x2f\x2a\x5e\x2f\x09\x09\x3a\x0a\x2a\x2f\x5e\x09\x3a\x0a"
		"\x2a\x2f\x2a\x2f\x09\x3a\x0a\x5e\x2a\x09\x3a\x0a\x2a\x2f\x5e";

	struct fy_parse_cfg cfg = {0};
	struct fy_document *fyd = NULL;

	cfg.flags = FYPCF_RESOLVE_DOCUMENT | FYPCF_DISABLE_ACCELERATORS |
		    FYPCF_SLOPPY_FLOW_INDENTATION | FYPCF_PREFER_RECURSIVE |
		    FYPCF_YPATH_ALIASES;

	// this must fail
	fyd = fy_document_build_from_string(&cfg, buf, FY_NT);
	ck_assert_ptr_eq(fyd, NULL);
}
END_TEST

START_TEST(fuzz_document_iterator_cleanup_uaf)
{
	/* Data from the issue report that triggers the merge-key path */
	char data[] =
		"\x0a\x25\x59\x41\x4d\x4c\x09\x31\x2e\x31\x0d\x2d\x2d\x2d\xe2\x80"
		"\xa8\x74\x74\x74\x74\x74\x74\x74\x74\x74\x74\x74\x74\x74\x74\x20"
		"\x20\x20\x20\x1a\x3a\x2d\x2d\x25\x20\x2d\x20\x22\x3a\x0d\x2d\x20"
		"\x3a\x2d\x2d\x20\x2d\x20\x2a\x2f\x5a\x0d\x3c\x3c\x3a\x0d\x2d\x20"
		"\x20\x20\x3a\x20\x20\x2d\x54\x41\x47\x2f\x3a\x0d\x3a\x20\x74\x74"
		"\x74\x74\x74\x74\x74\x74\x74\x74\x74\x74\x74\x3a\x20\x3a\x20\x3a"
		"\x09\x32\x2e\x31\x0d\x2d\x2d\x2d\xe2\x80\xa8\x74\x2d\x20\x2d\x20"
		"\x2a\x2f\x3a\x0d\x3c\x3c\x3a\x0d\x2d\x20\x20\x7b\x7d\x20\x69\x2a"
		"\x21\x7b\x7b\x7d\x7a\x5d\x42\x7b\x7d\x7b\x35\x0a\x74\x74\x74\x74"
		"\x74\x74\x74\x74\x74\x74\x74\x74\x74\x3a\x20\x3a\x20\x3a\x20\x3a"
		"\x20\x3a\x20\x3a\x20\x6d\x20\x43\x20\x74\x74\x74\x74\x74\x74\x74"
		"\x74\x74\x74\x74\x74\x74\x3a\x20\x3a\x20\x3a\x09\x32\x2e\x31\x0d"
		"\x2d\x2d\x2d\xe2\x80\xa8\x74\x2d\x20\x2d\x20\x2a\x2f\x3a\x0d\x3c"
		"\x3c\x3a\x0d\x2d\x20\x20\x74\x74\x74\x74\x74\x74\x74\x74\x74\x74"
		"\x74\x74\x74\x3a\x20\x3a\x20\x3a\x20\x3a\x20\x3a\x20\x3a\x20\x6d"
		"\x20\x43\x20\x3a\x20\x3a\x20\x2d\x20\x00\x00\x00\x00\x00\x00\x00"
		"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
		"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
		"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
		"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
		"\x00\x00\x00\x00\x00\x00\x00\x00";

	struct fy_parser *fyp = NULL;
	struct fy_parse_cfg cfg = {0};
	struct fy_event *fyev = NULL;
	int rc;

	cfg.flags = FYPCF_RESOLVE_DOCUMENT | FYPCF_PARSE_COMMENTS |
		    FYPCF_DISABLE_BUFFERING | FYPCF_SLOPPY_FLOW_INDENTATION;

	fyp = fy_parser_create(&cfg);
	ck_assert_ptr_ne(fyp, NULL);

	rc = fy_parser_set_string(fyp, data, FY_NT);
	ck_assert_int_eq(rc, 0);

	while ((fyev = fy_parser_parse(fyp)) != NULL) {
		fy_event_get_comments(fyev);
		fy_parser_event_free(fyp, fyev);
	}

	fy_parser_destroy(fyp);
}
END_TEST

START_TEST(fuzz_emit_mapping_memory_leak)
{
	char data[] =
		"\x3a\x0a\x0a\x77\x7b\x3a\x0a\x7b\x7d\x3a\x0a\x0a\x0a\x7b\x7d\x3a"
		"\x0a\x3a\x3a\x0a\x0a\x0a\x7b\x7d\x3a\x0a\x3a\x0a\x7b\x7d\x3a\x0a"
		"\x0a\x7b\x7d\x3a\x0a\x3d\x7b\x7b\x7d\x3a\x0a\x3a\x3a\x0a\x0a\x3a"
		"\x3a\x0a\x0a\x0a\x7b\x7d\x3a\x0a\x3a\x0a\x4a\x7b\x7d\x3a\x0a\x0a"
		"\x7b\x7d\x3a\x0a\x0a\x77\x7b\x3a\x0a\x7b\x7d\x3a\x0a\x0a\x0a\x7b"
		"\x7d\x3a\x0a\x3a\x3a\x0a\x0a\x0a\x7b\x7d\x3a\x0a\x3a\x0a\x7b\x7d"
		"\x3a\x0a\x0a\x7b\x7d\x3a\x0a\x3d\x7b\x7b\x7d\x3a\x0a\x3a\x3a\x0a"
		"\x0a\x0a\x7b\x7d\x3a\x0a\x3a\x0a\x7b\x7d\x3a\x0a\x0a\x7b\x7d\x3a"
		"\x0a\x3a\x0a\x7b\x7d\x3a\x0a\x0a\x7b\x7d\x3a\x0a\x0a\x77\x7b\x3a"
		"\x0a\x7b\x7d\x3a\x0a\x0a\x0a\x7b\x7d\x3a\x0a\x3a\x3a\x0a\x0a\x0a"
		"\x7b\x7d\x3a\x0a\x3a\x0a\x7b\x7d\x3a\x0a\x3a\x0a\x7b\x7d\x3a\x0a"
		"\x0a\x7b\x7d\x3a\x0a\x0a\x77\x7b\x3a\x0a\x7b\x7d\x3a\x0a\x0a\x0a"
		"\x7b\x7d\x3a\x0a\x3a\x3a\x0a\x0a\x0a\x7b\x7d\x3a\x0a\x3a\x0a\x7b"
		"\x7d\x3a\x0a\x0a\x7b\x7d\x3a\x0a\x3d\x7b\x7b\x7d\x3a\x0a\x3a\x3a"
		"\x0a\x0a\x0a\x7b\x7d\x3a\x0a\x3a\x0a\x7b\x7d\x3a\x0a\x0a\x7b\x7d"
		"\x3a\x0a\x3a\x0a\x7b\x7d\x3a\x0a\x0a\x7b\x7d\x3a\x0a\x0a\x77\x7b"
		"\x3a\x0a\x7b\x7d\x3a\x0a\x0a\x0a\x7b\x7d\x3a\x0a\x3a\x3a\x0a\x0a"
		"\x0a\x7b\x7d\x3a\x0a\x3a\x0a\x7b\x7d\x3a\x0a\x0a\x7b\x7d\x3a\x0a"
		"\x2d\x2d\x2d\x7b\x7d\x3a\x0a\x3d\x7b\x7b\x7d\x3a\x0a\x3a\x3a\x0a"
		"\x0a\x0a\x7b\x7d\x3a\x0a\x3a\x0a\x7b\x7d\x3a\x0a\x0a\x7b\x7d\x3a"
		"\x0a\x3d\x7b\x7d\x3a\x0a\x3f";

	struct fy_parse_cfg cfg = {
		.flags = FYPCF_YPATH_ALIASES | FYPCF_ALLOW_DUPLICATE_KEYS
	};
	struct fy_document *fyd;
	char *result;

	fyd = fy_document_build_from_string(&cfg, data, FY_NT);
	ck_assert_ptr_ne(fyd, NULL);

	result = fy_emit_document_to_string(fyd, FYECF_SORT_KEYS | FYECF_MODE_JSON_TP);
	ck_assert_ptr_ne(result, NULL);
	free(result);
	fy_document_destroy(fyd);
}
END_TEST

START_TEST(fuzz_walk_number_to_expr_nonfinite)
{
	char data[] = "\x2a\x28\x6e\x2a\x28\x2d\x30\x2f\x30\x29\x2f\x2a\x2a";
	struct fy_parse_cfg cfg = {
		.flags = FYPCF_COLLECT_DIAG | FYPCF_RESOLVE_DOCUMENT |
			 FYPCF_PREFER_RECURSIVE | FYPCF_JSON_NONE |
			 FYPCF_YPATH_ALIASES,
	};
	struct fy_document *fyd = NULL;
	FILE *fp = NULL;
	int rc;

	fyd = fy_document_build_from_string(&cfg, data, FY_NT);
	if (!fyd)
		return;

	fp = fopen("/dev/null", "w");
	ck_assert_ptr_ne(fp, NULL);

	rc = fy_emit_document_to_fp(fyd,
		FYECF_STRIP_LABELS | FYECF_WIDTH_DEFAULT | FYECF_WIDTH_80 |
		FYECF_WIDTH_INF | FYECF_MODE_FLOW | FYECF_MODE_FLOW_ONELINE |
		FYECF_MODE_JSON | FYECF_MODE_JSON_TP | FYECF_MODE_JSON_ONELINE |
		FYECF_MODE_DEJSON | FYECF_MODE_FLOW_COMPACT |
		FYECF_MODE_JSON_COMPACT | FYECF_DOC_START_MARK_OFF,
		fp);
	(void)rc;

	fclose(fp);
	fy_document_destroy(fyd);
}
END_TEST

START_TEST(fuzz_path_expr_execute_oom)
{
	char data[] =
		"\x2f\x2f\x2c\x2e\x2f\x2f\x2f\x2f\x2e\x2c\x2e\x2f\x2e\x2c\x2e\x2f"
		"\x2f\x2f\x2f\x2e\x2c\x33\x3a\x32\x2c\x2e\x2f\x2f\x2e\x2c\x2e\x2f"
		"\x2f\x2e\x2c\x2e\x2c\x2e\x31\x2f\x2f\x2f\x2e\x2c\x2e\x2f\x2f\x2f"
		"\x2f\x2e\x2c\x33\x3a\x32\x2c\x2f\x2e\x2c\x2e\x2f\x2e\x2c\x2e\x2f"
		"\x2f\x2f\x2f\x2e\x2c\x33\x3a\x32\x2c\x2e\x2f\x2f\x2e\x2c\x2e\x2f"
		"\x2f\x2e\x2c\x2e\x2c\x2e\x31\x2f\x2f\x2f\x2e\x2c\x2e\x2f\x2f\x2f"
		"\x2f\x2e\x2c\x33\x3a\x32\x2c\x2e\x2f\x2f\x2e\x2c\x2e\x2f\x2f\x2e"
		"\x2c\x2e\x2c\x2e\x31\x2f\x2f\x2e\x2c\x2e\x2f\x2e\x2c\x2e\x2f\x2e"
		"\x2c\x2e\x2f\x2f\x2e\x2c\x2e\x2f\x2f\x2e\x2c\x2e\x2c\x2e\x31\x2f"
		"\x2f\x2e\x2c\x2e\x2f\x2e\x2c\x2e\x2f\x2e\x2c\x2e\x2c\x2e\x2f\x2a";
	struct fy_document *fyd = NULL;
	struct fy_node *fyn, *root, *node;

	fyd = fy_document_create(NULL);
	ck_assert_ptr_ne(fyd, NULL);

	fyn = fy_node_create_sequence(fyd);
	ck_assert_ptr_ne(fyn, NULL);
	fy_document_set_root(fyd, fyn);

	root = fy_document_root(fyd);
	ck_assert_ptr_ne(root, NULL);

	node = fy_node_by_path(root, data, FY_NT,
			FYNWF_FOLLOW | FYNWF_PTR_JSON |
			FYNWF_PTR_RELJSON | FYNWF_PTR_YPATH);
	(void)node;

	fy_document_destroy(fyd);
}
END_TEST

START_TEST(fuzz_anchor_accel_cleanup_scalar_borrowed_input)
{
	char *doc_str;
	struct fy_document *fyd = NULL;
	struct fy_parse_cfg cfg = { .flags = FYPCF_PREFER_RECURSIVE | FYPCF_JSON_NONE };

	doc_str = strdup("&a foo");
	ck_assert_ptr_ne(doc_str, NULL);

	fyd = fy_document_build_from_string(&cfg, doc_str, FY_NT);
	ck_assert_ptr_ne(fyd, NULL);

	free(doc_str);
	fy_document_destroy(fyd);
}
END_TEST

START_TEST(fuzz_anchor_accel_cleanup_mapping_borrowed_input)
{
	char *doc_str;
	struct fy_document *fyd = NULL;
	struct fy_node *removed_val = NULL;
	struct fy_parse_cfg cfg = { .flags = FYPCF_PREFER_RECURSIVE | FYPCF_JSON_NONE };
	struct fy_node *root, *key_node;

	doc_str = strdup("{ &a foo: bar }");
	ck_assert_ptr_ne(doc_str, NULL);

	fyd = fy_document_build_from_string(&cfg, doc_str, FY_NT);
	ck_assert_ptr_ne(fyd, NULL);

	root = fy_document_root(fyd);
	ck_assert_ptr_ne(root, NULL);
	ck_assert(fy_node_is_mapping(root));

	key_node = fy_node_build_from_string(fyd, "foo", FY_NT);
	ck_assert_ptr_ne(key_node, NULL);
	removed_val = fy_node_mapping_remove_by_key(root, key_node);
	fy_node_free(key_node);

	fy_node_free(removed_val);
	free(doc_str);
	fy_document_destroy(fyd);
}
END_TEST

void libfyaml_case_fuzzing(struct fy_check_suite *cs)
{
	struct fy_check_testcase *ctc;

	ctc = fy_check_suite_add_test_case(cs, "fuzzing");

	fy_check_testcase_add_test(ctc, fuzz_resolve_aliases_stars_amps);
	fy_check_testcase_add_test(ctc, fuzz_resolve_disable_buffering_colon_star);
	fy_check_testcase_add_test(ctc, fuzz_resolve_aliases_special_chars);
	fy_check_testcase_add_test(ctc, fuzz_emit_null_document);
#ifdef __linux__
	fy_check_testcase_add_test(ctc, fuzz_node_build_fp_invalid_data);
#endif
	fy_check_testcase_add_test(ctc, fuzz_recursive_resolve_anchors_aliases);
#ifdef __linux__
	fy_check_testcase_add_test(ctc, fuzz_node_build_fp_emoji_invalid_utf8);
#endif
	fy_check_testcase_add_test(ctc, fuzz_emit_event_invalid_scalar_style);
	fy_check_testcase_add_test(ctc, fuzz_recursive_resolve_binary_data);
	fy_check_testcase_add_test(ctc, fuzz_node_by_path_ypath_sequence);
	fy_check_testcase_add_test(ctc, fuzz_token_iter_getc_after_read);
	fy_check_testcase_add_test(ctc, fuzz_resolve_collect_diag_colon_star);
	fy_check_testcase_add_test(ctc, fuzz_collect_diag_colon_star);
	fy_check_testcase_add_test(ctc, fuzz_emit_strip_empty_kv_many_modes);
	fy_check_testcase_add_test(ctc, fuzz_path_expr_triple_star);
	fy_check_testcase_add_test(ctc, fuzz_resolve_recursive_star_slash_bang);
	fy_check_testcase_add_test(ctc, fuzz_node_by_path_star_at);
	fy_check_testcase_add_test(ctc, fuzz_node_by_path_double_star_at_emit);
	fy_check_testcase_add_test(ctc, fuzz_node_by_path_star_underscore_sequence);
	fy_check_testcase_add_test(ctc, fuzz_ypath_aliases_complex_pattern);
	fy_check_testcase_add_test(ctc, fuzz_sloppy_flow_disable_flags);
	fy_check_testcase_add_test(ctc, fuzz_disable_recycling_ypath_aliases);
#ifdef __linux__
	fy_check_testcase_add_test(ctc, fuzz_build_from_fp_sloppy_flow);
	fy_check_testcase_add_test(ctc, fuzz_node_build_fp_flow_mapping);
#endif
	fy_check_testcase_add_test(ctc, fuzz_complex_anchors_recursive_buffering);
	fy_check_testcase_add_test(ctc, fuzz_node_build_string_block_scalar);
	fy_check_testcase_add_test(ctc, fuzz_node_by_path_dot_slash_emit);
	fy_check_testcase_add_test(ctc, fuzz_node_by_path_parens_sequence);
	fy_check_testcase_add_test(ctc, fuzz_parse_comments_recursive_emit);
#ifdef __linux__
	fy_check_testcase_add_test(ctc, fuzz_build_from_fp_recursive_duplicate_keys);
#endif
#if defined(__linux__)
	fy_check_testcase_add_test(ctc, fuzz_parser_event_loop_block_scalar);
#endif
	fy_check_testcase_add_test(ctc, fuzz_collect_diag_parse_comments_sequence);
	fy_check_testcase_add_test(ctc, fuzz_node_compare_clone_quoted_scalar);
	fy_check_testcase_add_test(ctc, fuzz_parse_comment_with_override);
	fy_check_testcase_add_test(ctc, fuzz_resolve_document_ypath_null_alias);
	fy_check_testcase_add_test(ctc, fuzz_path_expr_build_bang_triple_star);
	fy_check_testcase_add_test(ctc, fuzz_resolve_recursive_ypath_aliases_dup_keys);
	fy_check_testcase_add_test(ctc, fuzz_disable_recycling_ypath_aliases_dup_keys);
	fy_check_testcase_add_test(ctc, fuzz_parse_comments_emit_many_modes);
	fy_check_testcase_add_test(ctc, fuzz_resolve_recursive_ypath_dup_keys_emit_fp);
#if defined(__linux__)
	fy_check_testcase_add_test(ctc, fuzz_build_from_fp_sloppy_recursive_ypath_aliases);
	fy_check_testcase_add_test(ctc, fuzz_build_from_fp_ypath_aliases_recursive);
#endif
	fy_check_testcase_add_test(ctc, fuzz_node_get_type_uaf);
	fy_check_testcase_add_test(ctc, fuzz_emit_node_to_string_uaf);
	fy_check_testcase_add_test(ctc, fuzz_document_iterator_cleanup_uaf);
	fy_check_testcase_add_test(ctc, fuzz_emit_mapping_memory_leak);
	fy_check_testcase_add_test(ctc, fuzz_walk_number_to_expr_nonfinite);
	fy_check_testcase_add_test(ctc, fuzz_path_expr_execute_oom);
	fy_check_testcase_add_test(ctc, fuzz_anchor_accel_cleanup_scalar_borrowed_input);
	fy_check_testcase_add_test(ctc, fuzz_anchor_accel_cleanup_mapping_borrowed_input);
}
