/*
 * DF-4496 — dce100_create_resource_pool error-path pool leak
 * -----------------------------------------------------------
 * Guest-side control-flow model proof (no AMD GPU required).
 *
 * Faithful control-flow model of:
 *   sys/dev/drm/amd/display/dc/dce100/dce100_resource.c:1056-1071
 *   (dce100_create_resource_pool / construct / destruct)
 * and its caller sys/dev/drm/amd/display/dc/core/dc_resource.c:125-128.
 *
 * dce100_create_resource_pool():
 *     pool = kzalloc(sizeof(struct dce110_resource_pool));  [1060-1061]
 *     if (!pool) return NULL;
 *     if (construct(num_virtual_links, dc, pool))           [1066]
 *         return &pool->base;
 *     BREAK_TO_DEBUGGER();                                  [1069]
 *     return NULL;    <-- pool NEVER freed  (LEAK)
 *
 * construct() failure (res_create_fail :1050-1053) runs destruct(pool)
 * (:678-745) which frees the *sub-objects* only — never `pool` itself
 * (dce100_destroy_resource_pool :838-845 does the kfree, but it is only
 * reachable through a pool that was returned successfully).
 * Caller dc_create_resource_pool (dc_resource.c:104-168) sees NULL and
 * cannot free an allocation it never received.
 *
 * struct dce110_resource_pool { struct resource_pool base; }
 * (dce110_resource.h:37-39) — the shell is the whole allocation.
 *
 * Build:  cc -O2 -Wall -o df4496 df4496.c
 * Run:    ./df4496
 * Success criterion (bug modelled): failure path ends with exactly ONE
 * live allocation (the pool shell) while all children were reclaimed;
 * success path ends with ZERO live allocations after destroy.
 */
#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;	/* timing_generators[0] stand-in */
	void *mem_input;	/* mis[0] stand-in */
	void *opp;		/* opps[0] stand-in */
};

static int fail_subobject;

/* model of destruct() — dce100_resource.c:678-745: frees CHILDREN only */
static void destruct(struct pool *pool)
{
	kfree0(&pool->opp);		/* :683-684 */
	kfree0(&pool->mem_input);	/* :692-695 */
	kfree0(&pool->timing_generator);/* :697-700 */
}

/* model of construct() — children created; any failure ->
 * res_create_fail: destruct(pool); return false                */
static int construct(struct pool *pool)
{
	pool->timing_generator = kzalloc(64);		/* :977-987 */
	if (pool->timing_generator == NULL)
		goto res_create_fail;

	if (!fail_subobject) {
		pool->mem_input = kzalloc(64);		/* :989-995 */
		if (pool->mem_input == NULL)
			goto res_create_fail;
		pool->opp = kzalloc(64);		/* :1013-1019 */
		if (pool->opp == NULL)
			goto res_create_fail;
	} else
		goto res_create_fail;	/* stand-in for any sub-create fail */
	return 1;

res_create_fail:				/* :1050-1053 */
	destruct(pool);
	return 0;
}

/* exact control-flow copy of dce100_create_resource_pool (:1056-1071) */
static struct pool *dce100_create_resource_pool_dfly(void)
{
	struct pool *pool = kzalloc(sizeof(*pool));	/* :1060-1061 */
	if (!pool)
		return NULL;				/* :1063-1064 */
	if (construct(pool))				/* :1066 (num_virtual_links,dc elided) */
		return pool;
	/* BREAK_TO_DEBUGGER(); no-op in release */
	return NULL;					/* :1070 — no kfree! */
}

/* what dce100_destroy_resource_pool (:838-845) does for a live pool */
static void pool_destroy(struct pool **pp)
{
	destruct(*pp);
	kfree0((void **)pp);				/* :843 */
}

int main(void)
{
	struct pool *p;

	/* --- success path: ownership passes to caller; dc destroy frees --- */
	fail_subobject = 0;
	p = dce100_create_resource_pool_dfly();
	printf("success path: pool=%p live_allocs=%ld (pool+3 children)\n",
	    (void *)p, live_allocs);
	pool_destroy(&p);		/* dc destroy -> 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 = dce100_create_resource_pool_dfly();
	printf("failure path: returned=%p live_allocs=%ld "
	    "(children reclaimed by destruct; 1 = ORPHANED pool shell)\n",
	    (void *)p, live_allocs);

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