~lbnz/xr0

7b59a9315518cd040cf3919ff84786681f0859bd — Claude Betz a month ago b7751e4
fix: parameter reference leaks

issue: https://github.com/xr0-org/xr0/issues/48
M include/state.h => include/state.h +9 -7
@@ 37,7 37,8 @@ enum execution_mode {
enum frame_kind {
	FRAME_NESTED,
	FRAME_INTERMEDIATE,
	FRAME_CALL
	FRAME_CALL,
	FRAME_SETUP
};

struct frame;


@@ 195,6 196,13 @@ state_return(struct state *);
struct ast_expr *
state_framecall(struct state *);

struct location;

bool
state_returnreferences(struct state *, struct location *);

bool
state_callerreferences(struct state *, struct location *);

/* FRAME DTO */



@@ 213,8 221,6 @@ frame_intermediate_create(char *name, struct ast_block *, enum execution_mode);

/* USED BY VALUE */

struct location;

struct location *
location_copy(struct location *);



@@ 228,10 234,6 @@ bool
location_references(struct location *l1, struct location *l2, struct state *,
		struct circuitbreaker *cb);

struct circuitbreaker;

struct circuitbreaker;

bool
location_referencesheap(struct location *, struct state *,
		struct circuitbreaker *);

M src/object/object.c => src/object/object.c +3 -1
@@ 610,7 610,9 @@ range_copy(struct range *r)
static struct range * 
range_permuteheaplocs(struct range *r, struct permutation *p)
{
	assert(false);
	return range_create(
		ast_expr_copy(r->size), location_permuteheap(r->loc, p)
	);
}

static struct int_arr *

M src/state/block.c => src/state/block.c +22 -1
@@ 14,6 14,7 @@

struct block {
	struct object_arr *arr;
	bool caller;
};

struct block *


@@ 21,6 22,16 @@ block_create()
{
	struct block *b = malloc(sizeof(struct block));
	b->arr = object_arr_create();
	b->caller = false;
	return b;
}

struct block *
block_callercreate()
{
	struct block *b = malloc(sizeof(struct block));
	b->arr = object_arr_create();
	b->caller = true;
	return b;
}



@@ 36,6 47,7 @@ block_copy(struct block *old)
{
	struct block *new = malloc(sizeof(struct block));
	new->arr = object_arr_copy(old->arr);
	new->caller = old->caller;
	return new;
}



@@ 44,6 56,7 @@ block_permuteheaplocs(struct block *old, struct permutation *p)
{
	struct block *new = malloc(sizeof(struct block));
	new->arr = object_arr_create();
	new->caller = old->caller;

	struct object **obj = object_arr_objects(old->arr);
	int n = object_arr_nobjects(old->arr);


@@ 144,6 157,12 @@ block_references(struct block *b, struct location *loc, struct state *s,
	return false;
}

bool
block_iscaller(struct block *b)
{
	return b->caller;
}

struct error *
block_range_alloc(struct block *b, struct ast_expr *lw, struct ast_expr *up,
		struct heap *heap)


@@ 160,7 179,9 @@ block_range_alloc(struct block *b, struct ast_expr *lw, struct ast_expr *up,
					ast_expr_copy(up),
					ast_expr_copy(lw)
				),
				heap_newblock(heap)
				b->caller
					? heap_newcallerblock(heap)
					: heap_newblock(heap)
			)
		)
	);

M src/state/block.h => src/state/block.h +6 -0
@@ 6,6 6,9 @@ struct block;
struct block *
block_create();

struct block *
block_callercreate();

void
block_destroy(struct block *);



@@ 37,6 40,9 @@ bool
block_references(struct block *, struct location *, struct state *,
		struct circuitbreaker *);

bool
block_iscaller(struct block *);

struct error *
block_range_alloc(struct block *b, struct ast_expr *lw, struct ast_expr *up,
		struct heap *heap);

M src/state/clump.c => src/state/clump.c +26 -1
@@ 51,7 51,7 @@ clump_copy(struct clump *c)
int
clump_newblock(struct clump *c)
{
	int address = block_arr_append(c->blocks, block_create());
	int address = block_arr_append(c->blocks, block_callercreate());

	int n = block_arr_nblocks(c->blocks);
	assert(n > 0);


@@ 67,3 67,28 @@ clump_getblock(struct clump *c, int address)
	}
	return block_arr_blocks(c->blocks)[address];
}

static bool
block_referenceswithcb(struct block *, struct location *, struct state *);

bool
clump_callerreferences(struct clump *c, struct location *loc, struct state *s)
{
	int n = block_arr_nblocks(c->blocks);
	struct block **arr = block_arr_blocks(c->blocks);	
	for (int i = 0; i < n; i++) {
		if (block_referenceswithcb(arr[i], loc, s)) {
			return true;
		}
	}
	return false;
}

static bool
block_referenceswithcb(struct block *b, struct location *loc, struct state *s)
{
	struct circuitbreaker *cb = circuitbreaker_create();
	bool ref = block_references(b, loc, s, cb);	
	circuitbreaker_destroy(cb);
	return ref;
}

M src/state/clump.h => src/state/clump.h +3 -0
@@ 25,4 25,7 @@ struct block;
struct block *
clump_getblock(struct clump *c, int address);

bool
clump_callerreferences(struct clump *, struct location *, struct state *);

#endif

M src/state/heap.c => src/state/heap.c +66 -8
@@ 2,6 2,7 @@
#include <stdio.h>
#include <stdbool.h>
#include <assert.h>
#include <string.h>
#include "ast.h"
#include "block.h"
#include "heap.h"


@@ 16,7 17,7 @@

struct heap {
	struct block_arr *blocks;
	bool *freed; /* array of same length as blocks */
	bool *freed;  /* array of same length as blocks */
};

struct heap *


@@ 65,8 66,14 @@ heap_str(struct heap *h, char *indent)
			continue;
		}
		char *block = block_str(arr[i]);
		strbuilder_printf(b, "%s%d: %s%s", indent, i, block,
			printdelim(h, i) ? "\n" : "");
		strbuilder_printf(
			b, "%s%d: %s%s",
			indent, i, (block_iscaller(arr[i]) ? "" : ""),
			block
		);
		if (printdelim(h, i)) {
			strbuilder_printf(b, "\n");
		}
		free(block);
	}
	return strbuilder_build(b);


@@ 84,7 91,6 @@ printdelim(struct heap *h, int start)
	return false;
}


struct heap *
heap_permute(struct heap *old, struct permutation *p)
{


@@ 103,6 109,40 @@ heap_permute(struct heap *old, struct permutation *p)
	return new;
}

/* foundmap: return int map of the given size with the values in the array set
 * to 1, and all the others to 0. */
static int *
foundmap(struct int_arr *, int size);

void
heap_fillorder(struct heap *h, struct int_arr *arr)
{
	int n = block_arr_nblocks(h->blocks);
	struct block **b = block_arr_blocks(h->blocks);
	int *found = foundmap(arr, n);
	for (int i = 0; i < n; i++) {
		if (!found[i]) {
			int_arr_append(arr, i);
		}
	}
	free(found);
}

static int *
foundmap(struct int_arr *arr, int size)
{
	int arr_len = int_arr_len(arr);
	int *arr_v = int_arr_arr(arr);
	assert(arr_len <= size);

	int *m = calloc(size, sizeof(int));
	for (int i = 0; i < arr_len; i++) {
		assert(arr_v[i] < size);
		m[arr_v[i]] = 1;
	}
	return m;
}

struct block_arr *
heap_blocks(struct heap *h)
{


@@ 119,7 159,20 @@ heap_newblock(struct heap *h)
	assert(n > 0);
	h->freed = realloc(h->freed, sizeof(bool) * n);
	h->freed[address] = false;
		
	return location_create_dynamic(
		address, ast_expr_constant_create(0)
	);
}

struct location *
heap_newcallerblock(struct heap *h)
{
	int address = block_arr_append(h->blocks, block_callercreate());

	int n = block_arr_nblocks(h->blocks);
	assert(n > 0);
	h->freed = realloc(h->freed, sizeof(bool) * n);
	h->freed[address] = false;
	return location_create_dynamic(
		address, ast_expr_constant_create(0)
	);


@@ 175,7 228,8 @@ heap_referenced(struct heap *h, struct state *s)
{
	int n = block_arr_nblocks(h->blocks);
	for (int i = 0; i < n; i++) {
		if (!h->freed[i] && !block_referenced(s, i)) {
		struct block *b = block_arr_blocks(h->blocks)[i];
		if (!h->freed[i] && !block_iscaller(b) && !block_referenced(s, i)) {
			return false;
		}
	}


@@ 188,9 242,13 @@ block_referenced(struct state *s, int addr)
	struct location *loc = location_create_dynamic(
		addr, ast_expr_constant_create(0)
	);
	bool referenced = state_references(s, loc); 
	bool return_references = state_returnreferences(s, loc);
	if (!return_references) {
		return state_callerreferences(s, loc);
	}
	
	location_destroy(loc);
	return referenced;
	return true;	
}



M src/state/heap.h => src/state/heap.h +6 -1
@@ 33,7 33,10 @@ struct block_arr *
heap_blocks(struct heap *);

struct location *
heap_newblock(struct heap *h);
heap_newblock(struct heap *);

struct location *
heap_newcallerblock(struct heap *);

struct block; 



@@ 52,6 55,8 @@ heap_deallocblock(struct heap *h, int block);
void
heap_undeclare(struct heap *, struct state *);

void
heap_fillorder(struct heap *, struct int_arr *);

/* TODO: extract to own file */


M src/state/location.c => src/state/location.c +12 -0
@@ 296,6 296,18 @@ location_equal(struct location *l1, struct location *l2)
}

bool
location_referencescaller(struct location *l1, struct location *l2, struct state *s,
		struct circuitbreaker *cb)
{
	if (location_equal(l1, l2)) {
		return true;
	}

	struct block *b = state_getblock(s, l1);
	return b && block_iscaller(b) && block_references(b, l2, s, cb);
}

bool
location_references(struct location *l1, struct location *l2, struct state *s,
		struct circuitbreaker *cb)
{

M src/state/location.h => src/state/location.h +4 -0
@@ 91,6 91,10 @@ bool
location_equal(struct location *loc1, struct location *loc2);

bool
location_referencescaller(struct location *l1, struct location *l2, struct state *s,
		struct circuitbreaker *cb);

bool
location_references(struct location *loc1, struct location *loc2, struct state *,
		struct circuitbreaker *cb);


M src/state/stack.c => src/state/stack.c +14 -23
@@ 301,6 301,18 @@ stack_islinear(struct stack *s)
	return s->kind == FRAME_INTERMEDIATE;
}

bool
stack_insetup(struct stack *s)
{	
	if (s->kind == FRAME_SETUP) {
		return true;
	}
	if (s->prev == NULL) {
		return false;
	}
	return stack_insetup(s->prev);
}

enum execution_mode
stack_execmode(struct stack *s)
{


@@ 473,27 485,6 @@ stack_popprep(struct stack *s, struct state *state)
	}
}

bool
stack_references(struct stack *s, struct location *loc, struct state *state)
{
	/* TODO: check globals */
	struct value *result = state_popregister(state);
	struct circuitbreaker *c = circuitbreaker_create();
	if (result && value_references(result, loc, state, c)) {
		return true;
	}

	struct map *m = s->varmap;
	for (int i = 0; i < m->n; i++) {
		struct variable *var = (struct variable *) m->entry[i].value;
		if (variable_isparam(var) && variable_references(var, loc, state)) {
			return true;
		}
	}

	return false;
}

struct block *
stack_getblock(struct stack *s, int address)
{


@@ 539,7 530,7 @@ frame_block_create(char *n, struct ast_block *b, enum execution_mode mode)
struct frame *
frame_setup_create(char *n, struct ast_block *b, enum execution_mode mode)
{
	struct frame* f = frame_create(n, b, NULL, mode, FRAME_NESTED);
	struct frame* f = frame_create(n, b, NULL, mode, FRAME_SETUP);
	f->advance = false;
	return f;
}


@@ 695,7 686,7 @@ variable_references(struct variable *v, struct location *loc, struct state *s)
	assert(location_type(loc) != LOCATION_VCONST);

	struct circuitbreaker *cb = circuitbreaker_create();
	bool ans = location_references(v->loc, loc, s, cb);
	bool ans = location_referencescaller(v->loc, loc, s, cb);
	circuitbreaker_destroy(cb);
	return ans;
}

M src/state/stack.h => src/state/stack.h +3 -3
@@ 80,6 80,9 @@ stack_undeclare(struct stack *stack, struct state *state);
bool
stack_isnested(struct stack *);

bool
stack_insetup(struct stack *);

struct error *
stack_trace(struct stack *, struct error *);



@@ 89,9 92,6 @@ stack_getvarmap(struct stack *);
struct variable *
stack_getvariable(struct stack *s, char *id);

bool
stack_references(struct stack *s, struct location *loc, struct state *state);

struct ast_expr;

struct block *

M src/state/state.c => src/state/state.c +33 -6
@@ 143,6 143,12 @@ state_islinear(struct state *s)
	return stack_islinear(s->stack);
}

static bool
state_insetup(struct state *s)
{
	return stack_insetup(s->stack);	
}

char *
state_execmode_str(enum execution_mode m)
{


@@ 558,6 564,9 @@ state_range_alloc(struct state *state, struct object *obj,
struct value *
state_alloc(struct state *state)
{
	if (state_insetup(state)) {
		return value_ptr_create(heap_newcallerblock(state->heap));
	}
	return value_ptr_create(heap_newblock(state->heap));
}



@@ 637,9 646,18 @@ state_location_destroy(struct location *loc)
}

bool
state_references(struct state *s, struct location *loc)
state_returnreferences(struct state *s, struct location *loc)
{
	return stack_references(s->stack, loc, s);
	struct circuitbreaker *c = circuitbreaker_create();
	bool refs = s->reg && value_references(s->reg, loc, s, c);
	circuitbreaker_destroy(c);
	return refs;
}

bool
state_callerreferences(struct state *s, struct location *loc)
{
	return clump_callerreferences(s->clump, loc, s);
}

bool


@@ 690,6 708,9 @@ state_unnest(struct state *s);
static void
state_permuteheap(struct state *, struct int_arr *);

static struct int_arr *
deriveorder(struct state *s);

static void
state_normalise(struct state *s)
{


@@ 698,13 719,19 @@ state_normalise(struct state *s)
	state_undeclarevars(s);
	state_popprops(s);
	if (s->reg) {
		struct circuitbreaker *cb = circuitbreaker_create();
		struct int_arr *arr = value_deriveorder(s->reg, cb, s);
		circuitbreaker_destroy(cb);
		state_permuteheap(s, arr);
		state_permuteheap(s, deriveorder(s));
	}
}

static struct int_arr *
deriveorder(struct state *s)
{
	struct circuitbreaker *cb = circuitbreaker_create();
	struct int_arr *arr = value_deriveorder(s->reg, cb, s);
	circuitbreaker_destroy(cb);
	heap_fillorder(s->heap, arr);
	return arr;
}

static void
state_permuteheap(struct state *s, struct int_arr *arr)

M tests/0-basic/400-out-of-order.x => tests/0-basic/400-out-of-order.x +3 -0
@@ 4,5 4,8 @@ void
f() ~ [ return malloc(1); ]
{
	free(malloc(1));

	free(malloc(1));
	free(malloc(1));
	return malloc(1);
}

A tests/6-preconditions/200-clump-no-leak.x => tests/6-preconditions/200-clump-no-leak.x +8 -0
@@ 0,0 1,8 @@
#include <stdlib.h>

notleak(void **p) ~ [
        setup: p = .clump(1);
        *p = malloc(1);
]{
        *p = malloc(1);
}

A tests/6-preconditions/201-clump-nested-not-leak.x => tests/6-preconditions/201-clump-nested-not-leak.x +14 -0
@@ 0,0 1,14 @@
#include <stdlib.h>

struct composite {
	void *p;
};

void
allocsub(struct composite *p) ~ [
	setup: p = .clump(1);
	p->p = malloc(1);
]{
	p->p = malloc(1);
}


A tests/6-preconditions/210-composite.x => tests/6-preconditions/210-composite.x +12 -0
@@ 0,0 1,12 @@
#include <stdlib.h>

struct composite {
	void *p;
};

notleak(struct composite *p) ~ [
	setup: p = .clump(1);
	p->p = malloc(1);
]{
	p->p = malloc(1);
}

A tests/6-preconditions/220-FAIL-leak.x => tests/6-preconditions/220-FAIL-leak.x +6 -0
@@ 0,0 1,6 @@
#include <stdlib.h>

leak(void *p) ~ [ p = malloc(1); ]
{
        p = malloc(1);
}

A tests/6-preconditions/220-FAIL-leak.x.EXPECTED => tests/6-preconditions/220-FAIL-leak.x.EXPECTED +1 -0
@@ 0,0 1,1 @@
leak: garbage on heap

A tests/6-preconditions/230-FAIL-side-effect-leak.x => tests/6-preconditions/230-FAIL-side-effect-leak.x +18 -0
@@ 0,0 1,18 @@
#include <stdlib.h>

void
notleak(void **p) ~ [
        setup: p = .clump(1);
        *p = malloc(1);
]{
        *p = malloc(1);
}

int
main()
{
	void *p;
	p = malloc(1);
	notleak(&p);
	free(p);
}

A tests/6-preconditions/230-FAIL-side-effect-leak.x.EXPECTED => tests/6-preconditions/230-FAIL-side-effect-leak.x.EXPECTED +1 -0
@@ 0,0 1,1 @@
main: garbage on heap

A tests/6-preconditions/231-still-referenced.x => tests/6-preconditions/231-still-referenced.x +21 -0
@@ 0,0 1,21 @@
#include <stdlib.h>

void
notleak(void **p) ~ [
        setup: p = .clump(1);
        *p = malloc(1);
]{
        *p = malloc(1);
}

int
main()
{
	void *p; void *q;

	p = malloc(1);
	q = p;
	notleak(&p);
	free(p);
	free(q);
}

M tests/run => tests/run +1 -1
@@ 54,7 54,7 @@ do
		fi
	else
		# silence is golden
		if [[ $output -eq 0 ]] 
		if [[ $(cat $tempfile) -eq 0 ]] 
		then
			npass=$((npass+1))
			printf "$pass"