/*
 * DF-4318 — dce80/81/83_create_resource_pool error-path pool leak
 * ---------------------------------------------------------------
 * Guest-side model proof (no AMD GPU required, no sanitizers required).
 *
 * Faithful control-flow model of:
 *   sys/dev/drm/amd/display/dc/dce80/dce80_resource.c:1052-1067
 *   (dce80_create_resource_pool / dce80_construct / destruct)
 * and its caller sys/dev/drm/amd/display/dc/core/dc.c:671-677.
 *
 * dce80_create_resource_pool():
 *     pool = kzalloc(sizeof(struct dce110_resource_pool));   [1056-1057]
 *     if (!pool) return NULL;
 *     if (dce80_construct(num_virtual_links, dc, pool))      [1062]
 *         return &pool->base;
 *     BREAK_TO_DEBUGGER();                                   [1065]
 *     return NULL;    <-- pool NEVER freed  (LEAK)
 *
 * dce80_construct() failure (res_create_fail :1047-1049) runs destruct(pool)
 * (:726-796) which frees the *sub-objects* only — never `pool` itself.
 * Caller dc_construct (dc.c:671-677) sees NULL, `goto fail; destruct(dc)`
 * and cannot free an allocation it never received.
 *
 * Build:  cc -O2 -Wall -o df4318 df4318.c
 * Run:    ./df4318
 * Success criterion (bug modelled): failure path ends with exactly ONE
 * live allocation (the ~2KB pool) while all children were reclaimed;
 * success path ends with ZERO live allocations.
 */
#include <stdlib.h>
#include <stdio.h>

static long live_allocs;

static void *kzalloc(size_t n)
{
	void *p = calloc(1, n);
	if (p) live_allocs++;
	return p;
}
static void kfree0(void **p)
{
	if (*p) { free(*p); (*p) = NULL; live_allocs--; }
}

struct pool {                       /* model of struct dce110_resource_pool */
	void *timing_generator;     /* mis[0]/timing_generators[0] stand-ins */
	void *mem_input;
};

static int fail_subobject;

/* model of destruct() — dce80_resource.c:726-796: frees CHILDREN only */
static void destruct(struct pool *pool)
{
	kfree0(&pool->timing_generator);
	kfree0(&pool->mem_input);
}

/* model of dce80_construct() — children created; any failure ->
 * res_create_fail: destruct(pool); return false                     */
static int dce80_construct(struct pool *pool)
{
	pool->timing_generator = kzalloc(64);
	if (pool->timing_generator == NULL)
		goto res_create_fail;
	if (!fail_subobject) {
		pool->mem_input = kzalloc(64);
		if (pool->mem_input == NULL)
			goto res_create_fail;
	} else
		goto res_create_fail;      /* stand-in for any sub-create fail */
	return 1;

res_create_fail:                     /* dce80_resource.c:1047-1049 */
	destruct(pool);
	return 0;
}

/* exact control-flow copy of dce80_create_resource_pool (:1052-1067) */
static struct pool *dce80_create_resource_pool_dfly(void)
{
	struct pool *pool = kzalloc(sizeof(*pool));       /* :1056-1057 */
	if (!pool)
		return NULL;
	if (dce80_construct(pool))                        /* :1062 */
		return pool;
	/* BREAK_TO_DEBUGGER(); no-op in release */
	return NULL;                                      /* :1066 — no kfree! */
}

/* what dce80_destroy_resource_pool (:841-848) would do for a live pool */
static void pool_destroy(struct pool **pp)
{
	destruct(*pp);
	kfree0((void **)pp);
}

int main(void)
{
	struct pool *p;

	/* --- success path: ownership passes to caller; dc destroy frees --- */
	fail_subobject = 0;
	p = dce80_create_resource_pool_dfly();
	printf("success path: pool=%p live_allocs=%ld (pool+2 children)\n",
	    (void *)p, live_allocs);
	pool_destroy(&p);                  /* dc destroy -> dce80_destroy_resource_pool */
	printf("after destroy: live_allocs=%ld (expect 0)\n", live_allocs);

	/* --- failure path: a sub-object create fails --- */
	fail_subobject = 1;
	p = dce80_create_resource_pool_dfly();
	printf("failure path: returned=%p live_allocs=%ld "
	    "(children reclaimed by destruct; 1 = ORPHANED pool)\n",
	    (void *)p, live_allocs);

	if (p == NULL && live_allocs == 1) {
		printf("\nLEAK CONFIRMED (model): struct dce110_resource_pool "
		    "is orphaned on construct() failure — "
		    "dce80_resource.c:1062-1066 lacks kfree(pool).\n");
		return 0;
	}
	printf("\nmodel did not reproduce expectation\n");
	return 1;
}
