You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Implements a static memory analyzer that tracks allocations,
deallocations,
and ownership transfer at compile time to prevent memory safety issues.
Key features:
- Memory leak detection for unfreed allocations
- Double-free prevention
- Use-after-free detection within function scope
- Ownership transfer tracking via #returns_ownership and
#takes_ownership
- Defer statement integration for automatic cleanup validation
- Transitive ownership warnings for pass-through functions
Tracking strategy:
- Allocations in #returns_ownership functions are NOT tracked internally
(caller inherits responsibility for returned resources)
- #takes_ownership parameters are NOT tracked as new allocations
(they receive pre-existing resources, not create new ones)
- Defer blocks add variables to deferred_frees list, processed at scope
exit
- Assignment expressions (e.g., a.buf = alloc()) now tracked, not just
variable initializers
Known limitations:
- Struct field allocations tracked at struct level, not per-field
- Conditional allocation paths may produce false positives
- No stack-vs-heap analysis (returning &local not detected)
- Array-of-pointers patterns not fully tracked
Fixes:
- Remove incorrect allocation tracking for #takes_ownership parameters
- Add allocation tracking to assignment expressions
- Properly handle defer blocks in ownership transfer logic
- Add transitive ownership validation in return statements
Documentation updated with how the analyzer works, its capabilities,
current limitations, and best practices for users.
a.buf = alloc(size); // Not tracked - inside #returns_ownership
1204
+
return a;
1205
+
}
1206
+
1207
+
const create_arena -> fn () Arena {
1208
+
return create_arena_sized(1024);
1209
+
// Warning: Should add #returns_ownership annotation
1210
+
// for API clarity (ownership is being passed through)
1211
+
}
1212
+
```
1213
+
1214
+
#### Current Limitations
1215
+
1216
+
**Known Edge Cases:**
1217
+
1218
+
The analyzer currently has limitations in these areas:
1219
+
1220
+
1.**Struct Field Granularity**: When tracking `a.buf = alloc(...)`, the analyzer tracks the entire struct `a`, not the specific field `a.buf`. This works for single-pointer structs but may cause issues with:
1221
+
```luma
1222
+
const Container -> struct {
1223
+
data1: *int,
1224
+
data2: *int
1225
+
};
1226
+
1227
+
let c: Container;
1228
+
c.data1 = alloc(10); // Tracked as "c"
1229
+
c.data2 = alloc(20); // Also tracked as "c" - potential confusion
1230
+
free(c.data1); // Marks "c" as freed, but c.data2 still allocated
1231
+
```
1232
+
1233
+
2.**Conditional Allocations**: The analyzer may report false positives for conditional paths:
1234
+
```luma
1235
+
let ptr: *int;
1236
+
if (condition) {
1237
+
ptr = alloc(sizeof<int>);
1238
+
}
1239
+
// May warn even if you don't need to free in else branch
1240
+
```
1241
+
1242
+
3.**Allocations in Loops**: Each loop iteration's allocations should be independent, but edge cases may exist:
1243
+
```luma
1244
+
loop [i: int = 0](i < 10) : (++i) {
1245
+
let temp: *int = alloc(4);
1246
+
// Use temp...
1247
+
free(temp); // Should work correctly
1248
+
}
1249
+
```
1250
+
1251
+
4.**Early Returns with Defer**: While generally working, complex control flow with multiple early returns may need testing:
1252
+
```luma
1253
+
const process -> fn () int {
1254
+
let a: *int = alloc(sizeof<int>);
1255
+
defer free(a);
1256
+
1257
+
if (error) { return -1; } // Defer should fire
1258
+
if (warning) { return 0; } // Defer should fire
1259
+
return 1; // Defer should fire
1260
+
}
1261
+
```
1262
+
1263
+
5.**Stack vs Heap**: The analyzer doesn't currently detect returning pointers to stack variables:
1264
+
```luma
1265
+
const dangerous -> fn () *int {
1266
+
let local: int = 42;
1267
+
return &local; // NOT DETECTED - returns dangling pointer
1268
+
}
1269
+
```
1270
+
1271
+
6.**Arrays of Pointers**: Complex allocation patterns may not be fully tracked:
1272
+
```luma
1273
+
let arr: [*int; 5];
1274
+
loop [i: int = 0](i < 5) : (++i) {
1275
+
arr[i] = alloc(sizeof<int>); // Each needs individual free
1276
+
}
1277
+
```
1278
+
1279
+
#### Best Practices
1280
+
1281
+
To work effectively with the analyzer:
1282
+
1283
+
1.**Use ownership annotations consistently**: Mark all functions that allocate and return resources with `#returns_ownership`
1284
+
2.**Use defer for cleanup**: Always pair allocations with `defer free()` for automatic cleanup
1285
+
3.**One allocation per variable**: Avoid reassigning pointer variables after allocation without freeing
1286
+
4.**Clear ownership semantics**: Document which functions own their pointer parameters vs. borrowing them
1287
+
5.**Test early returns**: Ensure defer statements properly handle all exit paths
1288
+
1289
+
The analyzer is conservative - it may report false positives to prevent missed leaks. When in doubt, it will warn about potential issues rather than silently allowing them.
0 commit comments