/*
 * DF-5690 — pass-2 differential + lifetime harness for
 * sys/libprop/prop_array_util.c (the prop_array_{get,set,add}_{u,}intN /
 * _bool / _cstring[_nocopy] helper family + prop_array_add_and_rel).
 *
 * Modes:
 *   matrix        plist-XML -> prop_array_internalize -> every typed getter,
 *                 differential against the DERIVED guard contract:
 *                   signed v:  intN accept iff INTN_MIN<=v<=INTN_MAX
 *                              uintN accept iff 0<=v<=INTN_MAX
 *                   unsigned u: intN accept iff u<=INTN_MAX
 *                               uintN accept iff u<=UINTN_MAX
 *                 plus wrong-type/empty/OOB/NULL type-guard sweep, bool and
 *                 cstring roundtrips, set/add/externalize roundtrips, and
 *                 the known DF-2240 add_uint signed-creator rows
 *                 (documented, not counted as failures).
 *   nolife        nocopy-variant lifetime hazards on the guard-page rig
 *                 (fork per case; SIGSEGV = deterministic trap):
 *                 T1 get_cstring_nocopy returns a borrowed pointer into the
 *                    string object's storage; replacing the slot frees it
 *                    -> use-after-free read.
 *                 T2 set_cstring_nocopy makes the string point at the
 *                    caller's buffer; the caller REUSES that buffer;
 *                    externalize (the prop_array_copyout_ioctl analog)
 *                    leaks the NEW buffer content (and re-reads to the
 *                    current NUL, ignoring the frozen ps_size).
 *                 T2b same, but the caller FREES the buffer; the borrowed
 *                    pointer is read with no intervening allocation
 *                    -> use-after-free read (externalize faults the same
 *                    page when its own mmap doesn't recycle the address).
 *                 T3 ps_size is frozen at set_cstring_nocopy time but
 *                    prop_string_cstring() strcpy()s to the CURRENT NUL of
 *                    the caller's buffer -> heap overflow (into guard page).
 *                 T4 control: copy-variant set + owner free -> no trap.
 *   nolife-fixed  same expectations with T2/T3 requiring NO trap (fix under
 *                 test: kernel-build nocopy-create copies instead of
 *                 borrowing); T1 still traps (documented caller contract).
 *
 * Build: see build.sh (same-source libprop rigs from /usr/src/sys/libprop).
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <inttypes.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <signal.h>
#include <unistd.h>

#include <libprop/proplib.h>

#ifdef USE_GA
#include "guardalloc.h"
#endif

static int checks, fails, knowns;

#define FAIL(...) do { printf("FAIL: " __VA_ARGS__); fails++; } while (0)
#define CHECK(cond, ...) do { checks++; if (!(cond)) { FAIL(__VA_ARGS__); } } while (0)

/* ---------------- plist construction ---------------- */

static const char plist_hdr[] =
"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
"<!DOCTYPE plist PUBLIC \"-//Apple Computer//DTD PLIST 1.0//EN\" "
"\"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n"
"<plist version=\"1.0\">\n";
static const char plist_ftr[] = "</plist>\n";

static prop_array_t
arr_from_elem(const char *elem)
{
	char xml[2048];
	prop_array_t a;

	snprintf(xml, sizeof(xml), "%s<array>\n%s\n</array>\n%s",
	    plist_hdr, elem, plist_ftr);
	a = prop_array_internalize(xml);
	if (a == NULL) {
		printf("FATAL: internalize failed for elem: %s\n", elem);
		exit(2);
	}
	return (a);
}

/* ---------------- per-size dispatch ---------------- */

static int64_t iN_max(int N)  { return N==8?INT8_MAX :N==16?INT16_MAX :N==32?INT32_MAX :INT64_MAX; }
static int64_t iN_min(int N)  { return N==8?INT8_MIN :N==16?INT16_MIN :N==32?INT32_MIN :INT64_MIN; }
static uint64_t uN_max(int N) { return N==8?UINT8_MAX:N==16?UINT16_MAX:N==32?UINT32_MAX:UINT64_MAX; }

static bool get_intN(int N, prop_array_t a, unsigned i, int64_t *v)
{
	int8_t a8; int16_t a16; int32_t a32; int64_t a64;
	switch (N) {
	case 8:  { bool r = prop_array_get_int8(a,i,&a8);   if(r)*v=a8;  return r; }
	case 16: { bool r = prop_array_get_int16(a,i,&a16); if(r)*v=a16; return r; }
	case 32: { bool r = prop_array_get_int32(a,i,&a32); if(r)*v=a32; return r; }
	default: { bool r = prop_array_get_int64(a,i,&a64); if(r)*v=a64; return r; }
	}
}

static bool get_uintN(int N, prop_array_t a, unsigned i, uint64_t *v)
{
	uint8_t a8; uint16_t a16; uint32_t a32; uint64_t a64;
	switch (N) {
	case 8:  { bool r = prop_array_get_uint8(a,i,&a8);   if(r)*v=a8;  return r; }
	case 16: { bool r = prop_array_get_uint16(a,i,&a16); if(r)*v=a16; return r; }
	case 32: { bool r = prop_array_get_uint32(a,i,&a32); if(r)*v=a32; return r; }
	default: { bool r = prop_array_get_uint64(a,i,&a64); if(r)*v=a64; return r; }
	}
}

static bool set_intN(int N, prop_array_t a, unsigned i, int64_t v)
{
	switch (N) {
	case 8:  return prop_array_set_int8(a,i,(int8_t)v);
	case 16: return prop_array_set_int16(a,i,(int16_t)v);
	case 32: return prop_array_set_int32(a,i,(int32_t)v);
	default: return prop_array_set_int64(a,i,v);
	}
}

static bool set_uintN(int N, prop_array_t a, unsigned i, uint64_t v)
{
	switch (N) {
	case 8:  return prop_array_set_uint8(a,i,(uint8_t)v);
	case 16: return prop_array_set_uint16(a,i,(uint16_t)v);
	case 32: return prop_array_set_uint32(a,i,(uint32_t)v);
	default: return prop_array_set_uint64(a,i,v);
	}
}

static bool add_intN(int N, prop_array_t a, int64_t v)
{
	switch (N) {
	case 8:  return prop_array_add_int8(a,(int8_t)v);
	case 16: return prop_array_add_int16(a,(int16_t)v);
	case 32: return prop_array_add_int32(a,(int32_t)v);
	default: return prop_array_add_int64(a,v);
	}
}

static bool add_uintN(int N, prop_array_t a, uint64_t v)
{
	switch (N) {
	case 8:  return prop_array_add_uint8(a,(uint8_t)v);
	case 16: return prop_array_add_uint16(a,(uint16_t)v);
	case 32: return prop_array_add_uint32(a,(uint32_t)v);
	default: return prop_array_add_uint64(a,v);
	}
}

static const char *
szname(int N) { return N==8?"8":N==16?"16":N==32?"32":"64"; }

/* ---------------- matrix mode ---------------- */

struct val { const char *elem; int is_u; int64_t sv; uint64_t uv; };

static const struct val vals[] = {
	/* signed decimal plist forms */
	{ "-129",			0, -129, 0 },
	{ "-128",			0, -128, 0 },
	{ "-1",				0, -1, 0 },
	{ "-0x1",			0, -1, 0 },
	{ "0",				0, 0, 0 },
	{ "1",				0, 1, 0 },
	{ "127",			0, 127, 0 },
	{ "128",			0, 128, 0 },
	{ "200",			0, 200, 0 },
	{ "-32768",			0, -32768, 0 },
	{ "32767",			0, 32767, 0 },
	{ "32768",			0, 32768, 0 },
	{ "-2147483648",		0, INT64_C(-2147483648), 0 },
	{ "2147483647",			0, INT64_C(2147483647), 0 },
	{ "2147483648",			0, INT64_C(2147483648), 0 },
	{ "-9223372036854775808",	0, INT64_MIN, 0 },
	{ "9223372036854775807",	0, INT64_MAX, 0 },
	/* unsigned hex plist forms (kernel: ONLY path producing unsigned) */
	{ "0x0",		1, 0, 0x0 },
	{ "0x7F",		1, 0, 0x7F },
	{ "0x80",		1, 0, 0x80 },
	{ "0xFF",		1, 0, 0xFF },
	{ "0x100",		1, 0, 0x100 },
	{ "0x7FFF",		1, 0, 0x7FFF },
	{ "0x8000",		1, 0, 0x8000 },
	{ "0xFFFF",		1, 0, 0xFFFF },
	{ "0x10000",		1, 0, 0x10000 },
	{ "0x7FFFFFFF",	1, 0, UINT64_C(0x7FFFFFFF) },
	{ "0x80000000",	1, 0, UINT64_C(0x80000000) },
	{ "0xFFFFFFFF",	1, 0, UINT64_C(0xFFFFFFFF) },
	{ "0x100000000",	1, 0, UINT64_C(0x100000000) },
	{ "0x7FFFFFFFFFFFFFFF",1, 0, UINT64_C(0x7FFFFFFFFFFFFFFF) },
	{ "0x8000000000000000",1, 0, UINT64_C(0x8000000000000000) },
	{ "0xFFFFFFFFFFFFFFFE",1, 0, UINT64_C(0xFFFFFFFFFFFFFFFE) },
	{ "0xFFFFFFFFFFFFFFFF",1, 0, UINT64_MAX },
};

static bool
exp_int(int N, const struct val *v)
{
	if (v->is_u)
		return (v->uv <= (uint64_t)iN_max(N));
	return (v->sv >= iN_min(N) && v->sv <= iN_max(N));
}

static bool
exp_uint(int N, const struct val *v)
{
	if (v->is_u)
		return (v->uv <= uN_max(N));
	return (v->sv >= 0 && v->sv <= iN_max(N));
}

static void
matrix_value(const struct val *v)
{
	char elem[64];
	prop_array_t a;
	char tag[64];

	snprintf(elem, sizeof(elem), "<integer>%s</integer>", v->elem);
	a = arr_from_elem(elem);

	for (int N = 8; N <= 64; N *= 2) {
		int64_t iv; uint64_t uv;
		bool r;

		r = get_intN(N, a, 0, &iv);
		snprintf(tag, sizeof(tag), "<integer>%s</integer> get_int%s",
		    v->elem, szname(N));
		CHECK(r == exp_int(N, v), "%s: ret=%d want=%d\n", tag, r, exp_int(N, v));
		if (r && exp_int(N, v))
			CHECK(iv == (v->is_u ? (int64_t)v->uv : v->sv),
			    "%s: value %" PRId64 " want %" PRId64 "\n", tag, iv,
			    v->is_u ? (int64_t)v->uv : v->sv);

		r = get_uintN(N, a, 0, &uv);
		snprintf(tag, sizeof(tag), "<integer>%s</integer> get_uint%s",
		    v->elem, szname(N));
		CHECK(r == exp_uint(N, v), "%s: ret=%d want=%d\n", tag, r, exp_uint(N, v));
		if (r && exp_uint(N, v))
			CHECK(uv == (v->is_u ? v->uv : (uint64_t)v->sv),
			    "%s: value %" PRIu64 " want %" PRIu64 "\n", tag, uv,
			    v->is_u ? v->uv : (uint64_t)v->sv);
	}

	/* numbers are never bools/strings */
	{
		bool b = true;
		CHECK(prop_array_get_bool(a, 0, &b) == false,
		    "<integer>%s</integer> get_bool must be false\n", v->elem);
		char *cp = (char *)0x1;
		CHECK(prop_array_get_cstring(a, 0, &cp) == false,
		    "<integer>%s</integer> get_cstring must be false\n", v->elem);
		const char *ncp = (const char *)0x1;
		CHECK(prop_array_get_cstring_nocopy(a, 0, &ncp) == false,
		    "<integer>%s</integer> get_cstring_nocopy must be false\n", v->elem);
	}
	prop_object_release(a);
}

static void
matrix_wrong_types(void)
{
	const char *elems[] = {
		"<string>7</string>",
		"<true/>",
		"<false/>",
		"<data>AAAA</data>",
		"<array><integer>5</integer></array>",
		"<dict><key>a</key><integer>5</integer></dict>",
	};

	for (size_t e = 0; e < sizeof(elems)/sizeof(elems[0]); e++) {
		prop_array_t a = arr_from_elem(elems[e]);
		bool is_bool = (strcmp(elems[e], "<true/>") == 0 ||
				strcmp(elems[e], "<false/>") == 0);
		bool is_str  = (strcmp(elems[e], "<string>7</string>") == 0);

		for (int N = 8; N <= 64; N *= 2) {
			int64_t iv; uint64_t uv;
			CHECK(get_intN(N, a, 0, &iv) == false,
			    "%s get_int%s must be false\n", elems[e], szname(N));
			CHECK(get_uintN(N, a, 0, &uv) == false,
			    "%s get_uint%s must be false\n", elems[e], szname(N));
		}
		{
			bool b = true;
			bool r = prop_array_get_bool(a, 0, &b);
			if (is_bool) {
				bool want = strcmp(elems[e], "<true/>") == 0;
				CHECK(r == true && b == want,
				    "%s get_bool: r=%d b=%d want r=1 b=%d\n",
				    elems[e], r, b, want);
			} else {
				CHECK(r == false, "%s get_bool must be false\n",
				    elems[e]);
			}
		}
		{
			char *cp = NULL;
			const char *ncp = NULL;
			bool r1 = prop_array_get_cstring(a, 0, &cp);
			bool r2 = prop_array_get_cstring_nocopy(a, 0, &ncp);
			if (is_str) {
				CHECK(r1 == true && cp != NULL &&
				    strcmp(cp, "7") == 0,
				    "%s get_cstring roundtrip\n", elems[e]);
				CHECK(r2 == true && ncp != NULL &&
				    strcmp(ncp, "7") == 0,
				    "%s get_cstring_nocopy roundtrip\n", elems[e]);
				free(cp);
			} else {
				CHECK(r1 == false, "%s get_cstring false\n",
				    elems[e]);
				CHECK(r2 == false,
				    "%s get_cstring_nocopy false\n", elems[e]);
			}
		}
		prop_object_release(a);
	}
}

static void
matrix_oob_null(void)
{
	prop_array_t a = arr_from_elem("<integer>5</integer>");
	unsigned bad[3] = { 1, 7, 0xFFFFFFFFu };

	for (int k = 0; k < 3; k++) {
		for (int N = 8; N <= 64; N *= 2) {
			int64_t iv; uint64_t uv;
			CHECK(get_intN(N, a, bad[k], &iv) == false,
			    "OOB idx=%u get_int%s false\n", bad[k], szname(N));
			CHECK(get_uintN(N, a, bad[k], &uv) == false,
			    "OOB idx=%u get_uint%s false\n", bad[k], szname(N));
		}
		{
			bool b = true;
			char *cp = (char *)0x1;
			const char *ncp = (const char *)0x1;
			CHECK(prop_array_get_bool(a, bad[k], &b) == false,
			    "OOB idx=%u get_bool false\n", bad[k]);
			CHECK(prop_array_get_cstring(a, bad[k], &cp) == false,
			    "OOB idx=%u get_cstring false\n", bad[k]);
			CHECK(prop_array_get_cstring_nocopy(a, bad[k], &ncp) ==
			    false, "OOB idx=%u get_cstring_nocopy false\n",
			    bad[k]);
		}
	}

	/* NULL array: every accessor must fail cleanly (type guards) */
	for (int N = 8; N <= 64; N *= 2) {
		int64_t iv; uint64_t uv;
		CHECK(get_intN(N, NULL, 0, &iv) == false,
		    "NULL array get_int%s false\n", szname(N));
		CHECK(get_uintN(N, NULL, 0, &uv) == false,
		    "NULL array get_uint%s false\n", szname(N));
		CHECK(set_intN(N, NULL, 0, 0) == false,
		    "NULL array set_int%s false\n", szname(N));
		CHECK(set_uintN(N, NULL, 0, 0) == false,
		    "NULL array set_uint%s false\n", szname(N));
		CHECK(add_intN(N, NULL, 0) == false,
		    "NULL array add_int%s false\n", szname(N));
		CHECK(add_uintN(N, NULL, 0) == false,
		    "NULL array add_uint%s false\n", szname(N));
	}
	{
		bool b = true;
		char *cp = (char *)0x1;
		const char *ncp = (const char *)0x1;
		CHECK(prop_array_get_bool(NULL, 0, &b) == false, "NULL get_bool\n");
		CHECK(prop_array_set_bool(NULL, 0, true) == false, "NULL set_bool\n");
		CHECK(prop_array_get_cstring(NULL, 0, &cp) == false, "NULL get_cstring\n");
		CHECK(prop_array_get_cstring_nocopy(NULL, 0, &ncp) == false,
		    "NULL get_cstring_nocopy\n");
		CHECK(prop_array_set_cstring(NULL, 0, "x") == false, "NULL set_cstring\n");
		CHECK(prop_array_set_cstring_nocopy(NULL, 0, "x") == false,
		    "NULL set_cstring_nocopy\n");
		CHECK(prop_array_add_and_rel(NULL, NULL) == false, "NULL add_and_rel\n");
	}
	prop_object_release(a);
}

static void
matrix_roundtrip(void)
{
	for (int N = 8; N <= 64; N *= 2) {
		int64_t svals[3]; uint64_t uvals[3];
		svals[0] = iN_min(N); svals[1] = 1; svals[2] = iN_max(N);
		uvals[0] = 0; uvals[1] = 1; uvals[2] = uN_max(N);

		for (int k = 0; k < 3; k++) {
			prop_array_t a = prop_array_create();
			prop_array_t b = prop_array_create();
			char *xml;
			int64_t iv; uint64_t uv;

			CHECK(set_intN(N, a, 0, svals[k]) == true,
			    "set_int%s(%" PRId64 ") true\n", szname(N), svals[k]);
			CHECK(get_intN(N, a, 0, &iv) == true && iv == svals[k],
			    "set->get_int%s(%" PRId64 ") roundtrip\n", szname(N),
			    svals[k]);
			CHECK(add_intN(N, b, svals[k]) == true, "add_int%s\n",
			    szname(N));
			xml = prop_array_externalize(b);
			CHECK(xml != NULL, "externalize int%s\n", szname(N));
			if (xml != NULL) {
				prop_array_t c = prop_array_internalize(xml);
				CHECK(c != NULL, "reinternalize int%s\n",
				    szname(N));
				if (c != NULL) {
					CHECK(get_intN(N, c, 0, &iv) == true &&
					    iv == svals[k],
					    "ext->int int%s(%" PRId64 ")\n",
					    szname(N), svals[k]);
					prop_object_release(c);
				}
				free(xml);
			}
			prop_object_release(a);
			prop_object_release(b);

			a = prop_array_create();
			b = prop_array_create();
			CHECK(set_uintN(N, a, 0, uvals[k]) == true,
			    "set_uint%s(%" PRIu64 ") true\n", szname(N),
			    uvals[k]);
			CHECK(get_uintN(N, a, 0, &uv) == true && uv == uvals[k],
			    "set->get_uint%s(%" PRIu64 ") roundtrip\n",
			    szname(N), uvals[k]);
			CHECK(add_uintN(N, b, uvals[k]) == true, "add_uint%s\n",
			    szname(N));
			xml = prop_array_externalize(b);
			CHECK(xml != NULL, "externalize uint%s\n", szname(N));
			if (xml != NULL) {
				prop_array_t c = prop_array_internalize(xml);
				CHECK(c != NULL, "reinternalize uint%s\n",
				    szname(N));
				if (c != NULL) {
					if (uvals[k] > (uint64_t)iN_max(N)) {
						/*
						 * KNOWN DF-2240: add_uintN uses
						 * the signed creator, so values
						 * > INTN_MAX externalize as a
						 * decimal signed number that no
						 * longer fits N unsigned bits
						 * when reinternalized.
						 */
						knowns++;
						printf("KNOWN(DF-2240): "
						    "add_uint%s(%" PRIu64 ") "
						    "ext->reint get ret=%d "
						    "(expected false)\n",
						    szname(N), uvals[k],
						    (int)get_uintN(N, c, 0,
						    &uv));
						CHECK(get_uintN(N, c, 0, &uv) ==
						    false,
						    "KNOWN(DF-2240 regression) "
						    "add_uint%s(%" PRIu64 ") "
						    "ext roundtrip\n",
						    szname(N), uvals[k]);
					} else {
						CHECK(get_uintN(N, c, 0, &uv) ==
						    true && uv == uvals[k],
						    "ext->int uint%s(%" PRIu64
						    ")\n", szname(N), uvals[k]);
					}
					prop_object_release(c);
				}
				free(xml);
			}
			prop_object_release(a);
			prop_object_release(b);
		}
	}

	/* bool + cstring roundtrips */
	{
		prop_array_t a = prop_array_create();
		bool b = false;
		CHECK(prop_array_set_bool(a, 0, true) == true, "set_bool\n");
		CHECK(prop_array_get_bool(a, 0, &b) == true && b == true,
		    "set->get_bool true\n");
		CHECK(prop_array_set_bool(a, 0, false) == true, "set_bool 2\n");
		CHECK(prop_array_get_bool(a, 0, &b) == true && b == false,
		    "set->get_bool false\n");
		prop_object_release(a);

		a = arr_from_elem("<true/>");
		CHECK(prop_array_get_bool(a, 0, &b) == true && b == true,
		    "plist true -> get_bool\n");
		prop_object_release(a);
		a = arr_from_elem("<false/>");
		CHECK(prop_array_get_bool(a, 0, &b) == true && b == false,
		    "plist false -> get_bool\n");
		prop_object_release(a);

		a = prop_array_create();
		char *cp = NULL;
		const char *ncp = NULL;
		CHECK(prop_array_set_cstring(a, 0, "hello") == true,
		    "set_cstring\n");
		CHECK(prop_array_get_cstring(a, 0, &cp) == true &&
		    strcmp(cp, "hello") == 0, "get_cstring roundtrip\n");
		CHECK(prop_array_get_cstring_nocopy(a, 0, &ncp) == true &&
		    strcmp(ncp, "hello") == 0, "get_cstring_nocopy roundtrip\n");
		free(cp);
		prop_object_release(a);
	}

	/* prop_array_add_and_rel: consumes exactly one reference */
	{
		prop_array_t a = prop_array_create();
		prop_dictionary_t d = prop_dictionary_create();
		CHECK(prop_array_add_and_rel(a, d) == true, "add_and_rel(dict)\n");
		CHECK(prop_array_count(a) == 1, "add_and_rel count\n");
		CHECK(prop_array_add_and_rel(a, NULL) == false,
		    "add_and_rel(NULL) false\n");
		CHECK(prop_array_count(a) == 1, "add_and_rel(NULL) count\n");
		/* failure path: immutable array -> add fails, object consumed */
		prop_array_make_immutable(a);
		d = prop_dictionary_create();
		CHECK(prop_array_add_and_rel(a, d) == false,
		    "add_and_rel(immutable) false\n");
		prop_object_release(a);
	}
}

static void
matrix_known_df2240(void)
{
	prop_array_t a = prop_array_create();
	uint64_t uv;

	knowns++;
	prop_array_add_uint64(a, UINT64_C(0x8000000000000000));
	printf("KNOWN(DF-2240): add_uint64(0x8000000000000000) -> "
	    "get_uint64 ret=%d (signed-creator: expected false)\n",
	    (int)prop_array_get_uint64(a, 0, &uv));
	prop_object_release(a);

	a = prop_array_create();
	knowns++;
	prop_array_add_uint32(a, 0x80000000u);
	uint32_t v32;
	printf("KNOWN(DF-2240): add_uint32(0x80000000) -> "
	    "get_uint32 ret=%d (expected false)\n",
	    (int)prop_array_get_uint32(a, 0, &v32));
	prop_object_release(a);

	/* set_uint uses the unsigned creator: correct */
	a = prop_array_create();
	checks++;
	if (!(set_uintN(64, a, 0, UINT64_C(0x8000000000000000)) == true &&
	      get_uintN(64, a, 0, &uv) == true &&
	      uv == UINT64_C(0x8000000000000000)))
		FAIL("set_uint64/get_uint64(2^63) roundtrip (set path correct)\n");
	prop_object_release(a);
}

/* like arr_from_elem but returns NULL instead of exiting */
static prop_array_t
arr_from_elem_or_null(const char *elem)
{
	char xml[2048];

	snprintf(xml, sizeof(xml), "%s<array>\n%s\n</array>\n%s",
	    plist_hdr, elem, plist_ftr);
	return (prop_array_internalize(xml));
}


static void
matrix_info_df2230(void)
{
	/*
	 * INFO cross-check (DF-2230, prop_number.c): a decimal value above
	 * INT64_MAX fails internalize in USERLAND (errno==ERANGE detectable)
	 * but silently saturates to signed INT64_MAX in the KERNEL build
	 * (ERANGE check compiled out).  Show the userland half here.
	 */
	prop_array_t a = arr_from_elem_or_null("18446744073709551615");
	if (a != NULL) {
		int64_t iv;
		printf("INFO(DF-2230 xref): decimal UINT64_MAX in USERLAND -> "
		    "unsigned object, get_int64 ret=%d val=%" PRId64
		    " (kernel build: saturates to signed INT64_MAX)\n",
		    (int)get_intN(64, a, 0, &iv), iv);
		prop_object_release(a);
	}
}

static int
run_matrix(void)
{
	size_t n = sizeof(vals)/sizeof(vals[0]);

	printf("== DF-5690 matrix: %" PRIdPTR " plist value forms x 8 numeric "
	    "getters ==\n", (intptr_t)n);
	for (size_t i = 0; i < n; i++)
		matrix_value(&vals[i]);
	printf("== wrong-type elements ==\n");
	matrix_wrong_types();
	printf("== OOB index + NULL array type guards ==\n");
	matrix_oob_null();
	printf("== set/add/externalize roundtrips ==\n");
	matrix_roundtrip();
	printf("== known DF-2240 rows (documented, not counted) ==\n");
	matrix_known_df2240();
	matrix_info_df2230();

	printf("MATRIX: %d checks, %d failures, %d known(DF-2240) rows\n",
	    checks, fails, knowns);
	return (fails == 0 ? 0 : 1);
}

/* ---------------- nolife mode (guard-page rig) ---------------- */

#ifdef USE_GA

static volatile const char *keep;
static volatile char sink;

static void
do_case(int id)
{
	prop_array_t a;
	const char *cp;
	char *owner, *xml, *copy;

	switch (id) {
	case 1:	/* T1: get_cstring_nocopy borrowed pointer dangles on replace */
		a = prop_array_create();
		prop_array_set_cstring(a, 0, "AAAAAAAAAAAAAAAA");
		cp = NULL;
		if (prop_array_get_cstring_nocopy(a, 0, &cp) != true || cp == NULL) {
			printf("T1 setup failed\n");
			fflush(stdout);
			exit(9);
		}
		printf("T1: borrowed cp=%p into string storage\n", (const void *)cp);
		fflush(stdout);
		prop_array_set_cstring(a, 0, "replacement");	/* frees old */
		fflush(stdout);
		keep = cp;
		sink = *keep;		/* UAF read: post-free page */
		printf("T1: NO TRAP reading freed cp[0]='%c'\n", (int)sink);
		fflush(stdout);
		break;
	case 2:	/* T2: set_cstring_nocopy borrows owner; owner REUSES buffer */
		owner = ga_malloc(64);
		strcpy(owner, "transient-buffer");
		a = prop_array_create();
		if (prop_array_set_cstring_nocopy(a, 0, owner) != true) {
			printf("T2 setup failed\n");
			fflush(stdout);
			exit(9);
		}
		printf("T2: string borrows owner=%p (PS_F_NOCOPY); "
		    "caller now REUSES the buffer\n", (void *)owner);
		fflush(stdout);
		/* kernel caller reuses its buffer between set and copyout */
		strcpy(owner, "PWNED-BY-BUFFER-REUSE");
		xml = prop_array_externalize(a);	/* copyout_ioctl analog */
		printf("T2: externalized:\n%s", xml ? xml : "(null)");
		fflush(stdout);
		/*
		 * exit 0  = plist leaks the REUSED buffer content (baseline)
		 * exit 3  = plist carries the frozen copy            (fixed)
		 */
		if (xml != NULL && strstr(xml,
		    "<string>PWNED-BY-BUFFER-REUSE</string>") != NULL)
			exit(0);
		if (xml != NULL && strstr(xml,
		    "<string>transient-buffer</string>") != NULL)
			exit(3);
		exit(6);
	case 5:	/* T2b: borrow + owner FREED + read with no intervening alloc */
		owner = ga_malloc(64);
		strcpy(owner, "transient-buffer");
		a = prop_array_create();
		if (prop_array_set_cstring_nocopy(a, 0, owner) != true) {
			printf("T2b setup failed\n");
			fflush(stdout);
			exit(9);
		}
		printf("T2b: string borrows owner=%p; caller FREES it\n",
		    (void *)owner);
		fflush(stdout);
		ga_free(owner);		/* kernel caller frees its buffer */
		cp = NULL;
		if (prop_array_get_cstring_nocopy(a, 0, &cp) != true ||
		    cp == NULL) {
			printf("T2b: get_cstring_nocopy failed\n");
			fflush(stdout);
			exit(9);
		}
		fflush(stdout);
		keep = cp;
		sink = *keep;		/* baseline: freed page -> SIGSEGV */
		/* fixed: string owns its copy -> intact content */
		if (strcmp(cp, "transient-buffer") == 0) {
			printf("T2b: NO TRAP, intact content \"%s\"\n", cp);
			fflush(stdout);
			exit(0);
		}
		printf("T2b: NO TRAP but wrong content \"%s\"\n", cp);
		fflush(stdout);
		exit(5);
	case 3:	/* T3: ps_size frozen at creation; strcpy to current NUL -> overflow */
		owner = ga_malloc(8192);
		memset(owner, 'B', 8191);
		owner[15] = '\0';	/* 15 chars at set time */
		a = prop_array_create();
		if (prop_array_set_cstring_nocopy(a, 0, owner) != true) {
			printf("T3 setup failed\n");
			fflush(stdout);
			exit(9);
		}
		printf("T3: ps_size=15 frozen; owner buffer now reused\n");
		fflush(stdout);
		owner[15] = 'B';	/* caller lengthens in same buffer */
		owner[8191] = '\0';
		copy = NULL;
		if (prop_array_get_cstring(a, 0, &copy) != true) {
			printf("T3: get_cstring failed\n");
			fflush(stdout);
			exit(9);
		}
		printf("T3: NO TRAP copy=%.16s... (len=%zu)\n", copy,
		    strlen(copy));
		fflush(stdout);
		/* fixed: frozen 15-byte copy, no overrun */
		if (strlen(copy) == 15)
			exit(0);
		exit(5);
	case 4:	/* T4 control: copy-variant set + owner free -> no trap */
		owner = ga_malloc(64);
		strcpy(owner, "control");
		a = prop_array_create();
		prop_array_set_cstring(a, 0, owner);	/* copies */
		ga_free(owner);
		xml = prop_array_externalize(a);
		printf("T4(control): externalize=%s", xml ? xml : "(null)");
		fflush(stdout);
		if (xml != NULL && strstr(xml, "<string>control</string>"))
			exit(0);
		exit(4);
	}
	fflush(stdout);
	exit(0);
}

static int
run_forked_case(int id)
{
	pid_t pid;
	int st;

	fflush(NULL);
	pid = fork();
	if (pid == 0) {
		do_case(id);
		_exit(0);	/* not reached */
	}
	if (waitpid(pid, &st, 0) < 0)
		return (-1);
	if (WIFSIGNALED(st) && WTERMSIG(st) == SIGSEGV)
		return (1);	/* deterministic guard/freed-page trap */
	if (WIFEXITED(st))
		return (WEXITSTATUS(st));
	return (-1);
}

static const char *
oc_label(int r)
{
	static char buf[32];

	switch (r) {
	case 1:	return ("TRAP(SIGSEGV)");
	case 0:	return ("exit=0");
	case -1: return ("wait-error");
	default:
		snprintf(buf, sizeof(buf), "exit=%d", r);
		return (buf);
	}
}

static int
run_nolife(int fixed)
{
	/*
	 * want codes: 1 = SIGSEGV trap; 0 = exit 0 (deterministic success);
	 * for T2: 0 = leaked reused-buffer content (baseline semantics),
	 *         3 = frozen copy (fixed semantics).
	 */
	struct { int id; const char *name; int want_base; int want_fixed; } cases[] = {
		{ 1, "T1  get_cstring_nocopy dangling read after slot replace", 1, 1 },
		{ 2, "T2  set_cstring_nocopy + buffer reuse + externalize",      0, 3 },
		{ 5, "T2b set_cstring_nocopy + buffer freed + nocopy read",      1, 0 },
		{ 3, "T3  set_cstring_nocopy frozen ps_size + strcpy overrun",   1, 0 },
		{ 4, "T4  control: copy-variant set + owner free",               0, 0 },
	};
	int bad = 0;

	printf("== DF-5690 nolife (%s): trap=SIGSEGV on guard/freed page ==\n",
	    fixed ? "FIXED build" : "BASELINE build");
	for (size_t i = 0; i < sizeof(cases)/sizeof(cases[0]); i++) {
		int r = run_forked_case(cases[i].id);
		int want = fixed ? cases[i].want_fixed : cases[i].want_base;
		printf("%s -> %-14s [%s want %s] %s\n", cases[i].name,
		    oc_label(r), fixed ? "fixed" : "base", oc_label(want),
		    r == want ? "OK" : "MISMATCH");
		if (r != want)
			bad++;
	}
	if (fixed)
		printf("note: T1 trap is the documented caller contract "
		    "(keep parent array alive); fix targets T2/T2b/T3 only.\n");
	printf("NOLIFE(%s): %s\n", fixed ? "fixed" : "base",
	    bad == 0 ? "all expectations met" : "MISMATCHES present");
	return (bad == 0 ? 0 : 1);
}
#endif /* USE_GA */

int
main(int argc, char **argv)
{
	if (argc < 2) {
		fprintf(stderr, "usage: %s matrix|nolife|nolife-fixed\n",
		    argv[0]);
		return (2);
	}
	if (strcmp(argv[1], "matrix") == 0)
		return (run_matrix());
#ifdef USE_GA
	if (strcmp(argv[1], "nolife") == 0)
		return (run_nolife(0));
	if (strcmp(argv[1], "nolife-fixed") == 0)
		return (run_nolife(1));
#endif
	fprintf(stderr, "mode %s not compiled in\n", argv[1]);
	return (2);
}
