ascii-chat 0.6.0
Real-time terminal-based video chat with ASCII art conversion
Loading...
Searching...
No Matches
Defer Statements

🔄 Go/Zig-style defer statements for automatic cleanup in C More...

Files

file  defer.h
 Defer macro definition for source-to-source transformation.
 

Macros

#define defer(action)
 Defer a cleanup action until function scope exit.
 

Detailed Description

🔄 Go/Zig-style defer statements for automatic cleanup in C

This header provides the defer() macro syntax that is recognized by the ascii-instr-defer tool. The tool transforms defer() calls into inlined cleanup code at all function exit points.

Example usage: FILE *f = fopen("file.txt", "r"); defer(fclose(f)); // Will be called when function exits

The defer transformer converts this to: FILE *f = fopen("file.txt", "r"); // ... at every return point and function end: { fclose(f); }

Note: This header only defines the macro for parsing. The actual cleanup code is generated by the transformer - no runtime library is needed.

Defer Statements

🔄 Overview

This module implements Go/Zig-style defer statements for C using Clang libTooling source-to-source transformation. Defer allows cleanup code to run automatically at function exit, regardless of the exit path (return, goto, end of function).

Key Design Decision: No runtime library is needed. The transformer directly inlines cleanup code at every exit point, making defer zero-overhead at runtime.

Implementation: src/tooling/defer/tool.cpp

Key Features:

  • Automatic cleanup at function exit
  • LIFO (last-in-first-out) execution order
  • Zero runtime overhead (cleanup inlined at compile time)
  • Automatic detection and transformation of files using defer()
  • CMake integration via cmake/tooling/Defer.cmake

Architecture

Source-to-Source Transformation (No Runtime)

The defer tool transforms source code by directly inlining cleanup code at every function exit point. No runtime library is needed.

Original Code:

void process_file(const char *path) {
FILE *f = fopen(path, "r");
defer(fclose(f)); // Cleanup happens automatically
if (!f) return; // fclose runs here
// ... process file ...
return; // fclose runs here too
}
#define defer(action)
Defer a cleanup action until function scope exit.
Definition defer.h:36

Transformed Code:

void process_file(const char *path) {
FILE *f = fopen(path, "r");
// defer(fclose(f)); removed - cleanup inlined below
if (!f) {
{ fclose(f); } // Inlined cleanup
return;
}
// ... process file ...
{ fclose(f); } // Inlined cleanup
return;
}

Transformation Rules:

  1. Defer detection: Match defer(expression); statements
  2. Exit point injection: Insert cleanup block { expression; } before:
    • Every return statement
    • End of function (implicit return)
    • Before goto targets outside the defer scope
  3. LIFO order: Multiple defers execute in reverse order (last-in-first-out)
  4. Remove defer statement: The original defer() call is removed

Header Stub (lib/tooling/defer/defer.h)

The header provides a no-op macro so IDEs and editors can parse code that uses defer():

#define defer(action) \
do { /* transformed by ascii-instr-defer */ } while (0)

This allows:

  • Syntax highlighting to work correctly
  • IDE code completion and error checking
  • Code to compile (as a no-op) without transformation for quick testing

Build Integration (cmake/tooling/Defer.cmake)

Defer transformation is enabled automatically when files contain defer() calls.

# Defer is automatically enabled when defer() is detected in source files
# No CMake option needed - just build normally
cmake -B build
cmake --build build

How it works:

  1. CMake scans source files for defer( usage
  2. Only files containing defer() are transformed
  3. Transformed files are written to build/defer_transformed/
  4. Build uses transformed sources instead of originals

Key features:

  • Incremental builds: Only re-transforms files that changed
  • Cached tool: The defer tool binary is cached in .deps-cache/defer-tool/
  • No runtime library: Cleanup code is inlined directly

Usage Examples

Basic Cleanup

void example() {
char *buffer = malloc(1024);
defer(free(buffer));
// ... use buffer ...
// free(buffer) happens automatically
}

Multiple Resources (LIFO order)

void multi_resource() {
FILE *f1 = fopen("file1.txt", "r");
defer(fclose(f1)); // Runs second (LIFO)
FILE *f2 = fopen("file2.txt", "w");
defer(fclose(f2)); // Runs first (LIFO)
// ... use files ...
// Order: fclose(f2) then fclose(f1)
}

Lock/Unlock Pattern

void critical_section(mutex_t *mtx) {
mutex_lock(mtx);
// ... critical section code ...
// mutex_unlock runs automatically on any exit path
}
#define mutex_lock(mutex)
Lock a mutex (with debug tracking in debug builds)
Definition mutex.h:140
pthread_mutex_t mutex_t
Mutex type (POSIX: pthread_mutex_t)
Definition mutex.h:38
#define mutex_unlock(mutex)
Unlock a mutex (with debug tracking in debug builds)
Definition mutex.h:175

Error Handling

asciichat_error_t process_data(void) {
int fd = open("data.bin", O_RDONLY);
if (fd < 0) {
return SET_ERRNO_SYS(ERROR_CONFIG, "Failed to open file");
}
defer(close(fd)); // Cleanup on any return path
uint8_t *buffer = SAFE_MALLOC(4096, uint8_t*);
defer(free(buffer));
if (read(fd, buffer, 4096) < 0) {
return SET_ERRNO_SYS(ERROR_OPERATION_FAILED, "Read failed");
// Both close(fd) and free(buffer) run here
}
return ASCIICHAT_OK;
// Both close(fd) and free(buffer) run here too
}
#define SAFE_MALLOC(size, cast)
Definition common.h:208
unsigned char uint8_t
Definition common.h:56
#define SET_ERRNO_SYS(code, context_msg,...)
Set error code with custom message and system error context.
asciichat_error_t
Error and exit codes - unified status values (0-255)
Definition error_codes.h:46
@ ASCIICHAT_OK
Definition error_codes.h:48
@ ERROR_CONFIG
Definition error_codes.h:54

Implementation Notes

Supported Expressions

The defer tool supports any single expression:

  1. Simple function calls: defer(fclose(file));
  2. Expressions with side effects: defer(printf("cleanup\n"));
  3. Multiple arguments: defer(cleanup_resource(res, flags));
  4. Compound expressions: defer((void)(count++, cleanup()));

Scope Handling

Defer applies to function scope, not block scope:

void example() {
{
FILE *f = fopen("file.txt", "r");
defer(fclose(f)); // Runs at function exit, not block exit
}
// ... more code ...
// fclose(f) runs here at function end
}

Performance

Since cleanup code is inlined:

  • Zero runtime overhead - No function call indirection
  • No memory allocation - No scope tracking structures
  • Compiler optimizations - Cleanup code can be optimized with surrounding code
  • Slight code size increase - Cleanup duplicated at each exit point

Comparison to Other Approaches

vs Manual Cleanup

Defer Advantages:

  • Cleanup code near allocation site (better readability)
  • Automatic handling of all exit paths (no missed cleanup)
  • LIFO order matches allocation order naturally

Manual Disadvantages:

  • Easy to miss cleanup paths (early returns, errors)
  • Cleanup far from allocation (hard to track)
  • Error-prone with complex control flow

vs Cleanup Attributes (GCC/Clang)

void example() {
__attribute__((cleanup(cleanup_fn))) int fd = open(...);
// Cleanup runs at scope exit
}
RGB pixel structure.
Definition video/image.h:80

Defer Advantages:

  • More flexible (can defer any expression, not just declarations)
  • Explicit control over cleanup order
  • Works with any compiler after transformation
  • No need to write separate cleanup functions

Attribute Disadvantages:

  • Compiler-specific (non-portable without transformation)
  • Tied to variable declarations only
  • Requires separate cleanup function for each type

Build System Details

File Locations

  • Tool source: src/tooling/defer/tool.cpp
  • Tool CMake: src/tooling/defer/CMakeLists.txt
  • Integration: cmake/tooling/Defer.cmake
  • Header stub: lib/tooling/defer/defer.h
  • Cached binary: .deps-cache/defer-tool/ascii-instr-defer
  • Transformed output: build/defer_transformed/

Troubleshooting

Defer not working:

  1. Check that the file contains defer( (CMake only transforms files with defer usage)
  2. Verify build completed: cmake --build build
  3. Check transformed output: cat build/defer_transformed/path/to/file.c

Tool not building:

  1. Requires LLVM/Clang development libraries
  2. Check: ls .deps-cache/defer-tool/ascii-instr-defer*
  3. Force rebuild: rm -rf .deps-cache/defer-tool && cmake --build build

IDE shows errors:

References

Macro Definition Documentation

◆ defer

#define defer (   action)

#include <defer.h>

Value:
do { /* transformed by ascii-instr-defer */ \
} while (0)

Defer a cleanup action until function scope exit.

Parameters
actionThe cleanup expression to execute (e.g., fclose(f), free(ptr))

The defer() macro marks cleanup code that should run when the function exits. The ascii-instr-defer tool transforms these into inlined cleanup code.

Multiple defers execute in LIFO (last-in-first-out) order.

Definition at line 36 of file defer.h.

37 { /* transformed by ascii-instr-defer */ \
38 } while (0)