Memeory leak, free, Polymorphic structs, using , using statement, Freeing memory of pointer in struct members

Node :: struct {
	lit: string,
	derived_node : Any_Node,
}
Statement :: struct {
	using node: Node,
	derived_stmt : Any_Stmt,
}
EOF_Stmt :: struct {
	using node: Node,
}
Expression :: struct {
	using node: Node,
}
Identifier :: struct {
	tok: tok.Token
}
LetStmt :: struct {
	using stmt: Statement,
	let_tok:    tok.Token,
	name:       ^Identifier,
	exp:        ^Expression,
}
Bad_Stmt :: struct {
	using stmt: Statement,
}
Any_Node :: union{
	^LetStmt,
	^EOF_Stmt,
	^Bad_Stmt,
}
Any_Stmt :: union {
	
	^LetStmt,
	^EOF_Stmt,
	^Bad_Stmt,

}

new_node:: proc($T: typeid) -> ^T {
	n, _ := new(T)
	n.derived_node = n
	base: ^Node = n // dummy check

	when intrinsics.type_has_field(T, "derived_expr") {
		n.derived_expr = n
	}
	when intrinsics.type_has_field(T, "derived_stmt") {
		n.derived_stmt = n
	}
	return n
}

using the style of ast in core:odin.

parse_program :: proc(p: ^Parser) -> (^Program, ParsingError) {
	prog := new(Program)
	for p.cur_tok.type != .EOF {
		stmt := parse_statement(p)
		append(&prog.stmts, stmt)
	}
	return prog, .OK
}
parse_statement :: proc(p: ^Parser) -> ^ast.Statement {
	if p.cur_tok.type == .LET {
		t_stmt := ast.new_node(ast.LetStmt)
        t_stmt.let_tok = p.cur_tok
		t_stmt.name = get_identifier(p)

		if p.next_tok.type != .ASSIGN {next_token(p);return ast.new_node(ast.Bad_Stmt)}

		exp := get_expression(p)
		return t_stmt
	}
	next_token(p)
	return ast.new_node(ast.Bad_Stmt)
}
get_identifier :: proc(p: ^Parser) -> ^ast.Identifier {
    next_token(p)
    ident  := new(ast.Identifier)
    ident.tok = p.cur_tok
	return ident
}
get_expression :: proc(p: ^Parser) -> ^ast.Expression {
    next_token(p)
	return nil
}

getting

[WARN ] --- [2024-12-28 12:36:03] <      576B/      616B> <      616B> (    1/   12) :: parser.my_test
        +++ leak        40B @ 0x1D2723E4078 [token.odin:90:make_new_lexer()]
        +++ leak        88B @ 0x1D2723E4118 [ast.odin:44:new_node()]
        +++ leak        24B @ 0x1D2723E4178 [parser.odin:53:get_identifier()]
        +++ leak        64B @ 0x1D2723E4198 [parser.odin:33:parse_program()]
        +++ leak        88B @ 0x1D2723E4250 [ast.odin:44:new_node()]
        +++ leak        24B @ 0x1D2723E42B0 [parser.odin:53:get_identifier()]
        +++ leak        48B @ 0x1D2723E42D0 [ast.odin:44:new_node()]
        +++ leak        48B @ 0x1D2723E4308 [ast.odin:44:new_node()]
        +++ leak        48B @ 0x1D2723E41E0 [ast.odin:44:new_node()]
        +++ leak        48B @ 0x1D2723E4218 [ast.odin:44:new_node()]
        +++ leak        56B @ 0x1D2723E40A8 [parser.odin:13:make_new_parser()]
parser  [|                       ]         1 :: [package done] (1 failed)

Finished 1 test in 3.135ms. The test failed.

from this

@(test)
my_test :: proc(t: ^testing.T) {
	l := tok.make_new_lexer(
		input = `  let five = 5
                        let ten = 10
                        `,
	)
	p := make_new_parser(l)
	program, err := parse_program(p)
	defer free(program)
	defer free_all(context.temp_allocator)


	for v, i in program.stmts {
		es, es_ok := v.derived_stmt.(^ast.LetStmt)
		testing.expectf(t, es_ok, "Token reading faild. Token was %v", program.stmts)
	}

}

test.
Some memory questions. in

Program :: struct {
	stmts: [dynamic]^ast.Statement,
}

do i have to make() the dynamic array like prog.stmts = make([dynamic]^ast.Statement) ? if do/dont it works either way.
if i want to free(let_statement)

LetStmt :: struct {
	using stmt: Statement,
	let_tok:    tok.Token,
	name:       ^Identifier,
	exp:        ^Expression,
}

do those name exp pointers also get deleted? if i delete something how do i find out if it got deleted? just assign somehting to the pointer and see if throws exception?
if it does not delete inner pointers how do i clean up something like this : stmts: [dynamic]^ast.Statement which will hold multiple types of statements which points to other expressin etc.?
I could not find or understand where is the memory cleanup in the "core:odin/parser" packages or in the test file. That would answer all my questions.
And in

new_node:: proc($T: typeid) -> ^T {
	n, _ := new(T)
	n.derived_node = n
	base: ^Node = n // dummy check
    
	when intrinsics.type_has_field(T, "derived_expr") {
		n.derived_expr = n
	}
	when intrinsics.type_has_field(T, "derived_stmt") {
		n.derived_stmt = n
	}
	return n
}

this, i tried new_node:: proc($T: typeid/Node) -> ^T { but if i do ast.new_node(ast.LetStmt) could not convert to type Node. But i thought it should. Because LetStmt is using stmt: Statement, and Statement is using node: Node.
Please ignore wrong logics of the code. I was just getting something to run first. And trying use polymorphic stuff in ast.

Note: Copy-pasta answer from Discord, in case someone needs it.

  1. if you don’t use make() it will default to use context.allocator on the first append() call. It’s fine, but make() is a bit more explicit and you don’t accidentally shoot yourself in the foot if you first append somewhere with a different allocator…

  2. no, free(program) will not free the fields, you have to delete/free every field yourself.

  3. if you want to get a ^Node back from new_node() see the demo, otherwise you can restrict $T, like new_node($T: typeid) -> T where intrinsics.type_is_subtype_of(T, Node).What you are trying to do with $T/Node is saying “I take anything as long as it is $T :: distinct Node”.

Addition to point 2:
Alternatively, you can use a growing arena from core:mem/virtual to allocate the statements, then you can just virtual.arena_free_all(&arena) instead of walking the tree.

1 Like