owned fields (25.5e): @owns(Kind) on a record's field, and owned_leaks

A field marked @owns(PhysShape) holds a handle its record owns. A function that releases one owned
field of a record (body_free(w, s.body)) and neither releases nor hands on another owned field of
the same record type (s.shape) gives the first back and loses the second - the phys_remove bug, at
compile time. ludic deps --resources (or --owned) lists them; owned_leaks is a number --check
ratchets. A test: the function that frees a solid's body alone is the one found; the one that frees
both is not.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Orkun ÇAKILKAYA 2026-09-28 17:01:28 +03:00
parent a0c6f8ce7f
commit b87ee96805
6 changed files with 74859 additions and 72140 deletions

View file

@ -60,6 +60,8 @@ var dp_fbirths: []pointer = new []pointer # 25.2: `fbirth <kind> <file:l
var dp_birth_leaks: int = 0
var dp_rdrops: []pointer = new []pointer # 25.5e: `rdrop <kind> <file:line> <fn> <how>`
var dp_resource_drops: int = 0
var dp_oleaks: []pointer = new []pointer # 25.5e: `oleak <Type.field> <file:line> <fn> <released>`
var dp_owned_leaks: int = 0
var dp_wreach_n: int = 0
var dp_wreach_at: pointer = ""
@ -102,6 +104,7 @@ function dp_load(path: pointer) -> bool {
dp_fkeeps = new []pointer; dp_frame_keeps = 0
dp_fbirths = new []pointer; dp_birth_leaks = 0
dp_rdrops = new []pointer; dp_resource_drops = 0
dp_oleaks = new []pointer; dp_owned_leaks = 0
dp_writes = new []pointer; dp_aliases = new []pointer; dp_wowner = new []pointer; dp_wname = new []pointer; dp_wfrom = new []pointer
let lines = split_lines(text)
for i in 0 .. len(lines) {
@ -131,6 +134,8 @@ function dp_load(path: pointer) -> bool {
if len(w) == 2 and w[0] == "birth_leaks" { dp_birth_leaks = s_to_int(w[1]) }
if len(w) >= 5 and w[0] == "rdrop" { push(dp_rdrops, lines[i]) }
if len(w) == 2 and w[0] == "resource_drops" { dp_resource_drops = s_to_int(w[1]) }
if len(w) >= 5 and w[0] == "oleak" { push(dp_oleaks, lines[i]) }
if len(w) == 2 and w[0] == "owned_leaks" { dp_owned_leaks = s_to_int(w[1]) }
if len(w) >= 5 and w[0] == "width" and w[4] == "0" and s_to_int(w[1]) > dp_wide_n {
dp_wide_n = s_to_int(w[1])
dp_wide_at = `{dp_fn_name(w[2])} ({w[3]})`
@ -330,7 +335,7 @@ var dp_vals: []int = null
var dp_cycle: []int = null
var dp_cycle_all: []int = null # the same with the layers' own edges counted
function dp_numbers() -> void {
dp_names = ["modules", "dependencies", "largest_cycle", "cross_writes", "globals_written_from_outside", "widest_function", "widest_reach", "widest_write_reach", "frame_allocs", "frame_keeps", "birth_leaks", "resource_drops"]
dp_names = ["modules", "dependencies", "largest_cycle", "cross_writes", "globals_written_from_outside", "widest_function", "widest_reach", "widest_write_reach", "frame_allocs", "frame_keeps", "birth_leaks", "resource_drops", "owned_leaks"]
var mods = 0
for k in 0 .. len(dp_mods) { if dp_own(k) { mods += 1 } }
var deps = 0
@ -352,7 +357,7 @@ function dp_numbers() -> void {
}
}
dp_reach_best()
dp_vals = [mods, deps, len(dp_cycle), writes, len(seen), dp_wide_n, dp_reach_n, dp_wreach_n, dp_frame_allocs, dp_frame_keeps, dp_birth_leaks, dp_resource_drops]
dp_vals = [mods, deps, len(dp_cycle), writes, len(seen), dp_wide_n, dp_reach_n, dp_wreach_n, dp_frame_allocs, dp_frame_keeps, dp_birth_leaks, dp_resource_drops, dp_owned_leaks]
}
# ---- the command --------------------------------------------------------------
@ -510,6 +515,7 @@ function cmd_deps() -> int {
else if a == "--keeps" { mode = "keeps" }
else if a == "--births" { mode = "births" }
else if a == "--resources" { mode = "resources" }
else if a == "--owned" { mode = "resources" }
else if a == "--uses" or a == "--check" or a == "--baseline" or a == "--widest" or a == "--reach" or a == "--wreach" {
if ai + 1 >= arg_count() { err(`ludic deps: {a} needs an argument\n`); return 2 }
mode = sslice(a, 2, slen(a))
@ -583,6 +589,11 @@ function cmd_deps() -> int {
print(`resource dropped: {w[1]} at {w[2]} in {w[3]}: {w[4]}`)
}
print(`resource_drops: {string(dp_resource_drops)}`)
for a in 0 .. len(dp_oleaks) {
let w = dp_words(dp_oleaks[a])
print(`owned field lost: {w[1]} at {w[2]} in {w[3]}, which releases {w[4]}`)
}
print(`owned_leaks: {string(dp_owned_leaks)}`)
return 0
}
if mode == "widest" { dp_top(s_to_int(arg2), 0); return 0 }