refactoringmemory

mgreenly's avatarfrom mgreenly

Memory Refactoring (talloc) refactoring skill

⭐1stars🔀0forks📁View on GitHub🕐Updated Jan 4, 2026

When & Why to Use This Skill

This Claude skill specializes in refactoring C code using the talloc hierarchical memory allocator. It identifies and fixes common memory management 'smells' such as orphaned allocations, incorrect error context lifetimes, and complex manual cleanup. By enforcing strict ownership and hierarchical patterns, it helps developers eliminate memory leaks and use-after-free vulnerabilities in complex C applications.

Use Cases

  • Migrating legacy C codebases from manual malloc/free management to the talloc hierarchical system for improved safety.
  • Fixing use-after-free crashes by ensuring error objects are correctly reparented before their local allocation contexts are freed.
  • Simplifying complex function cleanup logic by introducing temporary contexts for intermediate allocations.
  • Restructuring data structures to ensure internal fields are correctly owned by their parent structs, enabling automatic recursive deallocation.
  • Auditing C code for 'orphaned' allocations that lack a proper parent context, preventing potential memory leaks in long-running processes.
namerefactoring/memory
descriptionMemory Refactoring (talloc) refactoring skill

Memory Refactoring (talloc)

talloc-specific refactoring patterns for ikigai. Focus on ownership, hierarchy, and error context lifetime.

Pattern 1: Fix Error Context Lifetime

Smell: Error allocated on context that gets freed before error is returned.

// BROKEN - use-after-free
res_t ik_foo_init(void *parent, foo_t **out) {
    foo_t *foo = talloc_zero_(parent, sizeof(foo_t));
    res_t result = ik_bar_init(foo, &foo->bar);  // Error on foo
    if (is_err(&result)) {
        talloc_free(foo);  // FREES THE ERROR!
        return result;     // Crash
    }
}

Fix A (Preferred): Pass parent for error allocation:

res_t ik_foo_init(void *parent, foo_t **out) {
    bar_t *bar = NULL;
    res_t result = ik_bar_init(parent, &bar);  // Error survives on parent
    if (is_err(&result)) return result;

    foo_t *foo = talloc_zero_(parent, sizeof(foo_t));
    talloc_steal(foo, bar);
    foo->bar = bar;
    *out = foo;
    return OK(*out);
}

Fix B (Fallback): Reparent error before freeing:

res_t ik_foo_init(void *parent, foo_t **out) {
    foo_t *foo = talloc_zero_(parent, sizeof(foo_t));
    res_t result = ik_bar_init(foo, &foo->bar);
    if (is_err(&result)) {
        talloc_steal(parent, result.err);  // Save error
        talloc_free(foo);
        return result;
    }
}

Pattern 2: Introduce Temp Context

Smell: Intermediate allocations leak or complicate cleanup.

// BEFORE - manual cleanup of each allocation
res_t process(void *ctx, input_t *in) {
    char *buf1 = talloc_array(ctx, char, 1024);
    char *buf2 = talloc_array(ctx, char, 2048);
    parsed_t *p = talloc(ctx, parsed_t);
    // ... work ...
    talloc_free(buf1);
    talloc_free(buf2);
    talloc_free(p);
    return OK(result);
}

After: Use temp context for intermediate work:

res_t process(void *ctx, input_t *in) {
    void *tmp = talloc_new(ctx);

    char *buf1 = talloc_array(tmp, char, 1024);
    char *buf2 = talloc_array(tmp, char, 2048);
    parsed_t *p = talloc(tmp, parsed_t);
    // ... work ...

    // Steal result to ctx if needed
    if (keep_result) talloc_steal(ctx, result);

    talloc_free(tmp);  // Clean all intermediates
    return OK(result);
}

Critical: Errors must be on ctx, not tmp. Pass ctx to functions that might fail.

Pattern 3: Add Missing Context Parameter

Smell: Function allocates internally without context parameter.

// BEFORE - hidden allocation
char *format_message(const char *fmt, ...) {
    char *buf = malloc(1024);  // Who frees this?
    // ...
    return buf;
}

After: Accept context, return on caller's hierarchy:

char *ik_format_msg(void *ctx, const char *fmt, ...) {
    char *buf = talloc_array(ctx, char, 1024);
    // ...
    return buf;  // Caller owns via ctx
}

Pattern 4: Fix Orphaned Allocations

Smell: Allocation on NULL or wrong parent.

// BEFORE - orphaned
void *ctx = talloc_new(NULL);  // Root context in non-main function

After: Receive parent from caller:

res_t ik_module_init(void *parent, module_t **out) {
    module_t *mod = talloc_zero_(parent, sizeof(module_t));
    // mod is child of parent, freed when parent freed
}

Pattern 5: Convert malloc to talloc

Smell: Raw malloc/free in codebase.

// BEFORE
char *buf = malloc(size);
// ... use buf ...
free(buf);

After:

char *buf = talloc_array(ctx, char, size);
// ... use buf ...
// Freed automatically with ctx, or explicitly:
talloc_free(buf);

Pattern 6: Proper Child Relationships

Smell: Struct fields allocated on wrong context.

// BEFORE - fields are siblings, not children
res_t init(void *ctx, foo_t **out) {
    foo_t *foo = talloc_zero_(ctx, sizeof(foo_t));
    foo->name = talloc_strdup(ctx, name);  // Sibling of foo!
    foo->data = talloc_array(ctx, char, size);  // Sibling of foo!
}

After: Fields are children of struct:

res_t init(void *ctx, foo_t **out) {
    foo_t *foo = talloc_zero_(ctx, sizeof(foo_t));
    foo->name = talloc_strdup(foo, name);  // Child of foo
    foo->data = talloc_array(foo, char, size);  // Child of foo
    // Now talloc_free(foo) frees everything
}

Refactoring Checklist

  1. Search for malloc( - Convert to talloc
  2. Search for talloc_new(NULL) - Should only be in main()
  3. Search for talloc_free after is_err - Check error context lifetime
  4. Review _init functions - Ensure ctx parameter, proper child allocation
  5. Check struct field allocation - Fields should be children of struct
  6. Look for cleanup code - Can it be simplified with temp context?