Skip to content

Commit c6efb63

Browse files
kevinburkesegmentclaude
authored andcommitted
fix: strdup ObjC strings returned across CGo boundary
UTF8String pointers into NSString buffers become dangling once the autorelease pool drains. Use strdup so callers own the memory, and pair each call site in Go with C.free via defer. Affected: getSystemInfo, getNSHomeDirectory, getNSCacheDirectory. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent a740511 commit c6efb63

5 files changed

Lines changed: 81 additions & 9 deletions

File tree

apps/finicky/src/util/directories.go

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,21 @@
11
package util
22

33
/*
4+
#include <stdlib.h>
45
#include "info.h"
56
*/
67
import "C"
78
import (
89
"fmt"
910
"strings"
11+
"unsafe"
1012
)
1113

1214
// UserHomeDir returns the user's home directory using NSHomeDirectory
1315
func UserHomeDir() (string, error) {
14-
dir := C.GoString(C.getNSHomeDirectory())
16+
cDir := C.getNSHomeDirectory()
17+
defer C.free(unsafe.Pointer(cDir))
18+
dir := C.GoString(cDir)
1519
if dir == "" {
1620
return "", fmt.Errorf("failed to get user home directory")
1721
}
@@ -32,7 +36,12 @@ func ShortenPath(path string) string {
3236

3337
// UserCacheDir returns the user's cache directory using NSSearchPathForDirectoriesInDomains
3438
func UserCacheDir() (string, error) {
35-
dir := C.GoString(C.getNSCacheDirectory())
39+
cDir := C.getNSCacheDirectory()
40+
if cDir == nil {
41+
return "", fmt.Errorf("failed to get user cache directory")
42+
}
43+
defer C.free(unsafe.Pointer(cDir))
44+
dir := C.GoString(cDir)
3645
if dir == "" {
3746
return "", fmt.Errorf("failed to get user cache directory")
3847
}

apps/finicky/src/util/info.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,8 @@ func GetModifierKeys() map[string]bool {
3939
// GetSystemInfo returns system information
4040
func GetSystemInfo() map[string]string {
4141
info := C.getSystemInfo()
42+
defer C.free(unsafe.Pointer(info.localizedName))
43+
defer C.free(unsafe.Pointer(info.name))
4244
return map[string]string{
4345
"localizedName": C.GoString(info.localizedName),
4446
"name": C.GoString(info.name),

apps/finicky/src/util/info.h

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,10 +24,10 @@ typedef struct {
2424
} PowerInfo;
2525

2626
ModifierKeys getModifierKeys(void);
27-
SystemInfo getSystemInfo(void);
27+
SystemInfo getSystemInfo(void); /* caller must free localizedName and name */
2828
PowerInfo getPowerInfo(void);
2929
_Bool isAppRunning(const char* identifier);
30-
const char* getNSHomeDirectory(void);
31-
const char* getNSCacheDirectory(void);
30+
const char* getNSHomeDirectory(void); /* caller must free */
31+
const char* getNSCacheDirectory(void); /* caller must free; may return NULL */
3232

3333
#endif /* INFO_H */

apps/finicky/src/util/info.m

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,8 @@ SystemInfo getSystemInfo() {
2424
NSString *nameStr = [currentHost name] ?: @"";
2525

2626
SystemInfo info = {
27-
.localizedName = [localizedNameStr UTF8String],
28-
.name = [nameStr UTF8String]
27+
.localizedName = strdup([localizedNameStr UTF8String]),
28+
.name = strdup([nameStr UTF8String])
2929
};
3030
return info;
3131
}
@@ -119,14 +119,14 @@ _Bool isAppRunning(const char* identifier) {
119119

120120
const char* getNSHomeDirectory(void) {
121121
NSString *homeDirString = NSHomeDirectory();
122-
return [homeDirString UTF8String];
122+
return strdup([homeDirString UTF8String]);
123123
}
124124

125125
const char* getNSCacheDirectory(void) {
126126
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, true);
127127
if (paths.count > 0) {
128128
NSString *cacheDirString = [paths objectAtIndex:0];
129-
return [cacheDirString UTF8String];
129+
return strdup([cacheDirString UTF8String]);
130130
}
131131
return NULL;
132132
}

apps/finicky/src/util/info_test.go

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
//go:build darwin
2+
3+
package util
4+
5+
import (
6+
"strings"
7+
"testing"
8+
)
9+
10+
func TestGetSystemInfo(t *testing.T) {
11+
info := GetSystemInfo()
12+
13+
if info["localizedName"] == "" {
14+
t.Error("localizedName is empty")
15+
}
16+
if info["name"] == "" {
17+
t.Error("name is empty")
18+
}
19+
}
20+
21+
func TestUserHomeDir(t *testing.T) {
22+
dir, err := UserHomeDir()
23+
if err != nil {
24+
t.Fatalf("unexpected error: %v", err)
25+
}
26+
if !strings.HasPrefix(dir, "/") {
27+
t.Errorf("expected absolute path, got %q", dir)
28+
}
29+
}
30+
31+
func TestUserCacheDir(t *testing.T) {
32+
dir, err := UserCacheDir()
33+
if err != nil {
34+
t.Fatalf("unexpected error: %v", err)
35+
}
36+
if !strings.HasPrefix(dir, "/") {
37+
t.Errorf("expected absolute path, got %q", dir)
38+
}
39+
}
40+
41+
func TestShortenPath(t *testing.T) {
42+
home, err := UserHomeDir()
43+
if err != nil {
44+
t.Skip("could not get home dir")
45+
}
46+
47+
cases := []struct {
48+
input string
49+
want string
50+
}{
51+
{home, "~"},
52+
{home + "/foo/bar", "~/foo/bar"},
53+
{"/other/path", "/other/path"},
54+
{"", ""},
55+
}
56+
for _, c := range cases {
57+
if got := ShortenPath(c.input); got != c.want {
58+
t.Errorf("ShortenPath(%q) = %q, want %q", c.input, got, c.want)
59+
}
60+
}
61+
}

0 commit comments

Comments
 (0)