From 7c7ddf46f61e50237c793f5b34b710b044a32b81 Mon Sep 17 00:00:00 2001 From: Spencer Brower Date: Fri, 12 Jun 2026 09:33:32 -0400 Subject: [PATCH] fix: Fixed memory leaks in `find_binary`. --- db.odin | 2 +- features.odin | 32 +++++++++++++++++++++----------- features_test.odin | 34 ++++++++++++++++++++++++++++++++++ scan.odin | 2 +- scan_test.odin | 2 +- 5 files changed, 58 insertions(+), 14 deletions(-) create mode 100644 features_test.odin diff --git a/db.odin b/db.odin index c5fd5d5..1750b3f 100644 --- a/db.odin +++ b/db.odin @@ -512,7 +512,7 @@ update_dir :: proc(f: ^EnvFile, new_dir: string) { find_moved_dirs :: proc(d: ^Db, f: ^EnvFile) -> ([dynamic]string, bool) { feats := check_features() - if !has_feature(feats, .Fd) || !has_feature(feats, .Git) { + if .Fd not_in feats || .Git not_in feats { fmt.println("Error: fd and git are required for moved dir detection") return {}, false } diff --git a/features.odin b/features.odin index db2a3d0..a6334c3 100644 --- a/features.odin +++ b/features.odin @@ -1,5 +1,7 @@ package main +import "base:runtime" +import "core:mem" import "core:os" import "core:strings" @@ -14,25 +16,36 @@ AvailableFeatures :: bit_set[Feature] check_features :: proc() -> AvailableFeatures { feats: AvailableFeatures - if find_binary("git") != "" { + s: mem.Scratch + mem.scratch_init(&s, 4 * mem.DEFAULT_PAGE_SIZE) + defer mem.scratch_destroy(&s) + + context.temp_allocator = mem.scratch_allocator(&s) + + path_env := os.get_env("PATH", context.temp_allocator) + paths := strings.split(path_env, ":", context.temp_allocator) + + if find_binary(paths, "git") != "" { feats += {.Git} } - if find_binary("fd") != "" { + if find_binary(paths, "fd") != "" { feats += {.Fd} } - if find_binary("age") != "" { + if find_binary(paths, "age") != "" { feats += {.Age} } return feats } -find_binary :: proc(name: string) -> string { - path_env := os.get_env("PATH", context.allocator) - paths := strings.split(path_env, ":") +find_binary :: proc( + paths: []string, + name: string, + allocator: runtime.Allocator = context.temp_allocator, +) -> string { for p in paths { - candidate := strings.join({strings.trim_right(p, "/"), name}, "/") - _, err := os.stat(candidate, context.allocator) + candidate := strings.join({strings.trim_right(p, "/"), name}, "/", allocator) + _, err := os.stat(candidate, allocator) if err == nil { return candidate } @@ -40,6 +53,3 @@ find_binary :: proc(name: string) -> string { return "" } -has_feature :: proc(feats: AvailableFeatures, f: Feature) -> bool { - return f in feats -} diff --git a/features_test.odin b/features_test.odin new file mode 100644 index 0000000..feaaf39 --- /dev/null +++ b/features_test.odin @@ -0,0 +1,34 @@ +package main + +import "core:os" +import "core:strings" +import "core:testing" + +@(test) +test_find_binary_exists :: proc(t: ^testing.T) { + path := os.get_env("PATH", context.allocator) + paths := strings.split(path, ":") + + result := find_binary(paths, "sh") + testing.expect(t, result != "", "sh should be found on PATH") +} + +@(test) +test_find_binary_not_exists :: proc(t: ^testing.T) { + old_path := os.get_env("PATH", context.allocator) + defer { + if old_path != "" { + os.set_env("PATH", old_path) + } + } + + os.set_env("PATH", "/tmp/envr-nope") + + path := os.get_env("PATH", context.allocator) + paths := strings.split(path, ":") + + + result := find_binary(paths, "no_such_binary_xyz") + testing.expect(t, result == "", "nonexistent binary should not be found") +} + diff --git a/scan.odin b/scan.odin index 7476342..8c99930 100644 --- a/scan.odin +++ b/scan.odin @@ -125,7 +125,7 @@ scan_path :: proc(search_path: string, cfg: Config) -> (paths: [dynamic]string, can_scan :: proc() -> bool { feats := check_features() - return has_feature(feats, .Fd) + return Feature.Fd in feats } find_unbacked :: proc(local_files: []string, db_files: []EnvFile) -> [dynamic]string { diff --git a/scan_test.odin b/scan_test.odin index f10da69..41bab06 100644 --- a/scan_test.odin +++ b/scan_test.odin @@ -16,7 +16,7 @@ test_scan_path_finds_gitignored_env_files :: proc(t: ^testing.T) { defer os.remove_all(base) git_init := os.Process_Desc{ - command = []string{"git", "init"}, + command = []string{"git", "-c", "advice.defaultBranchName=false", "init"}, working_dir = base, stdout = os.stderr, stderr = os.stderr,