# Synta C Memory Management Guide
**Table of Contents** *generated with [DocToc](https://github.com/thlorenz/doctoc)*
- [Overview](#overview)
- [Key Principles](#key-principles)
- [Ownership Model](#ownership-model)
- [Library-Owned (You Must Free)](#library-owned-you-must-free)
- [Caller-Owned (You Provide Storage)](#caller-owned-you-provide-storage)
- [Borrowed References (Do Not Free)](#borrowed-references-do-not-free)
- [Consumed Objects](#consumed-objects)
- [Memory Patterns](#memory-patterns)
- [Pattern 1: Simple Decode](#pattern-1-simple-decode)
- [Pattern 2: Simple Encode](#pattern-2-simple-encode)
- [Pattern 3: Nested Structures](#pattern-3-nested-structures)
- [Pattern 4: Error Handling](#pattern-4-error-handling)
- [Pattern 5: String Decoding](#pattern-5-string-decoding)
- [Pattern 6: OID Usage](#pattern-6-oid-usage)
- [Common Pitfalls](#common-pitfalls)
- [Pitfall 1: Double Free](#pitfall-1-double-free)
- [Pitfall 2: Use After Free](#pitfall-2-use-after-free)
- [Pitfall 3: Memory Leak](#pitfall-3-memory-leak)
- [Pitfall 4: Freeing Borrowed Data](#pitfall-4-freeing-borrowed-data)
- [Pitfall 5: Using Consumed Encoder](#pitfall-5-using-consumed-encoder)
- [Pitfall 6: Not Checking owned Flag](#pitfall-6-not-checking-owned-flag)
- [RAII Wrappers for C++](#raii-wrappers-for-c)
- [Debugging](#debugging)
- [Valgrind](#valgrind)
- [AddressSanitizer](#addresssanitizer)
- [Debug Checklist](#debug-checklist)
- [Memory Debugging Tools](#memory-debugging-tools)
- [See Also](#see-also)
Understanding memory ownership and lifetime management in the Synta C API.
## Table of Contents
- [Overview](#overview)
- [Ownership Model](#ownership-model)
- [Memory Patterns](#memory-patterns)
- [Common Pitfalls](#common-pitfalls)
- [RAII Wrappers for C++](#raii-wrappers-for-c)
- [Debugging](#debugging)
## Overview
The Synta C API follows clear ownership rules to prevent memory leaks and use-after-free bugs. Understanding these rules is essential for correct usage.
### Key Principles
1. **Explicit Ownership**: Clear who owns what
2. **Free What You Create**: If a function returns a pointer, you must free it
3. **Don't Free Borrowed Data**: Some functions return borrowed references
4. **One Free Per Allocation**: Never double-free
## Ownership Model
### Library-Owned (You Must Free)
These functions allocate memory that the caller must free:
| `synta_decoder_new()` | `synta_decoder_free()` |
| `synta_decoder_new_with_config()` | `synta_decoder_free()` |
| `synta_decoder_enter_sequence()` | `synta_decoder_free()` |
| `synta_decoder_enter_set()` | `synta_decoder_free()` |
| `synta_decoder_enter_constructed()` | `synta_decoder_free()` |
| `synta_encoder_new()` | `synta_encoder_free()` or `synta_encoder_finish()` |
| `synta_decode_integer()` | `synta_integer_free()` |
| `synta_decode_octet_string()` | `synta_octet_string_free()` |
| `synta_decode_object_identifier()` | `synta_oid_free()` |
| `synta_integer_new_*()` | `synta_integer_free()` |
| `synta_octet_string_new()` | `synta_octet_string_free()` |
| `synta_oid_new()` | `synta_oid_free()` |
| `synta_oid_from_string()` | `synta_oid_free()` |
| `synta_encoder_finish()` (output) | `synta_byte_array_free()` |
### Caller-Owned (You Provide Storage)
These functions write to caller-provided storage:
```c
// Caller provides storage for the output
bool value;
synta_decode_boolean(decoder, &value);
SyntaByteArray output;
synta_decode_utf8_string(decoder, &output);
// Must call synta_byte_array_free(&output) if output.owned != 0
SyntaTag tag;
synta_decoder_peek_tag(decoder, &tag);
// No cleanup needed, tag is a plain struct
```
### Borrowed References (Do Not Free)
These functions return pointers to data owned by another object:
```c
// Returns pointer to internal data - DO NOT FREE
const uint8_t *data = synta_octet_string_data(octet_string);
// Returns pointer to internal array - DO NOT FREE
const uint32_t *components;
size_t len;
synta_oid_components(oid, &components, &len);
// Returns static string - DO NOT FREE
const char *msg = synta_error_message(err);
```
### Consumed Objects
Some functions consume their arguments:
```c
SyntaEncoder *encoder = synta_encoder_new(SyntaEncoding_Der);
synta_encode_integer_i64(encoder, 42);
SyntaByteArray output = {0};
synta_encoder_finish(encoder, &output); // encoder is CONSUMED
// DO NOT use encoder after this point
// DO NOT call synta_encoder_free(encoder)
synta_byte_array_free(&output); // Free the output
```
## Memory Patterns
### Pattern 1: Simple Decode
```c
// Allocate decoder
const uint8_t data[] = {0x02, 0x01, 0x2A};
SyntaDecoder *decoder = synta_decoder_new(data, sizeof(data), SyntaEncoding_Der);
if (!decoder) {
return ERROR;
}
// Decode value
SyntaInteger *integer = NULL;
SyntaErrorCode err = synta_decode_integer(decoder, &integer);
if (err != SyntaErrorCode_Success) {
synta_decoder_free(decoder); // Clean up decoder
return ERROR;
}
// Use integer
int64_t value;
synta_integer_to_i64(integer, &value);
printf("Value: %lld\n", (long long)value);
// Clean up
synta_integer_free(integer);
synta_decoder_free(decoder);
```
### Pattern 2: Simple Encode
```c
// Allocate encoder
SyntaEncoder *encoder = synta_encoder_new(SyntaEncoding_Der);
if (!encoder) {
return ERROR;
}
// Encode values
synta_encode_integer_i64(encoder, 42);
// Finish and get output
SyntaByteArray output = {0};
if (synta_encoder_finish(encoder, &output) != SyntaErrorCode_Success) {
// encoder was consumed even on error
return ERROR;
}
// encoder is consumed, don't use or free it
// Use output
write_to_file(output.data, output.len);
// Clean up
synta_byte_array_free(&output);
```
### Pattern 3: Nested Structures
```c
SyntaDecoder *decoder = synta_decoder_new(data, len, SyntaEncoding_Der);
if (!decoder) {
return ERROR;
}
// Enter outer SEQUENCE
SyntaDecoder *outer = NULL;
if (synta_decoder_enter_sequence(decoder, &outer) != SyntaErrorCode_Success) {
synta_decoder_free(decoder);
return ERROR;
}
// Enter inner SEQUENCE
SyntaDecoder *inner = NULL;
if (synta_decoder_enter_sequence(outer, &inner) != SyntaErrorCode_Success) {
synta_decoder_free(outer);
synta_decoder_free(decoder);
return ERROR;
}
// Decode from inner
SyntaInteger *value = NULL;
synta_decode_integer(inner, &value);
// Clean up in reverse order
synta_integer_free(value);
synta_decoder_free(inner);
synta_decoder_free(outer);
synta_decoder_free(decoder);
```
### Pattern 4: Error Handling
```c
SyntaDecoder *decoder = NULL;
SyntaInteger *integer = NULL;
int result = -1;
decoder = synta_decoder_new(data, len, SyntaEncoding_Der);
if (!decoder) {
goto cleanup;
}
if (synta_decode_integer(decoder, &integer) != SyntaErrorCode_Success) {
goto cleanup;
}
int64_t value;
if (synta_integer_to_i64(integer, &value) != SyntaErrorCode_Success) {
goto cleanup;
}
// Success
printf("Value: %lld\n", (long long)value);
result = 0;
cleanup:
synta_integer_free(integer);
synta_decoder_free(decoder);
return result;
```
### Pattern 5: String Decoding
```c
SyntaByteArray string_data = {0};
if (synta_decode_utf8_string(decoder, &string_data) == SyntaErrorCode_Success) {
// string_data.data points to UTF-8 bytes
// string_data.len is the length
// string_data.owned != 0 means we need to free it
printf("String: %.*s\n", (int)string_data.len, string_data.data);
// Free if owned
if (string_data.owned) {
synta_byte_array_free(&string_data);
}
}
```
### Pattern 6: OID Usage
```c
// Create OID from string
SyntaObjectIdentifier *oid = synta_oid_from_string("1.2.840.113549.1.1.1");
if (!oid) {
fprintf(stderr, "Invalid OID\n");
return ERROR;
}
// Use OID
synta_encode_object_identifier(encoder, oid);
// Get components (borrowed pointer - don't free)
const uint32_t *components;
size_t len;
synta_oid_components(oid, &components, &len);
for (size_t i = 0; i < len; i++) {
printf("%u ", components[i]);
}
// Convert to string
char buffer[256];
if (synta_oid_to_string(oid, buffer, sizeof(buffer)) > 0) {
printf("OID: %s\n", buffer);
}
// Clean up
synta_oid_free(oid);
```
## Common Pitfalls
### Pitfall 1: Double Free
```c
// WRONG
SyntaInteger *integer = synta_integer_new_i64(42);
synta_integer_free(integer);
synta_integer_free(integer); // CRASH! Double free
// RIGHT
SyntaInteger *integer = synta_integer_new_i64(42);
synta_integer_free(integer);
integer = NULL; // Prevent accidental reuse
```
### Pitfall 2: Use After Free
```c
// WRONG
SyntaDecoder *decoder = synta_decoder_new(data, len, SyntaEncoding_Der);
synta_decoder_free(decoder);
synta_decode_integer(decoder, &integer); // CRASH! Use after free
// RIGHT
SyntaDecoder *decoder = synta_decoder_new(data, len, SyntaEncoding_Der);
synta_decode_integer(decoder, &integer); // Use first
synta_decoder_free(decoder); // Then free
```
### Pitfall 3: Memory Leak
```c
// WRONG - leaks decoder on error
SyntaDecoder *decoder = synta_decoder_new(data, len, SyntaEncoding_Der);
SyntaInteger *integer;
if (synta_decode_integer(decoder, &integer) != SyntaErrorCode_Success) {
return ERROR; // LEAK! decoder not freed
}
synta_decoder_free(decoder);
// RIGHT - always clean up
SyntaDecoder *decoder = synta_decoder_new(data, len, SyntaEncoding_Der);
SyntaInteger *integer;
if (synta_decode_integer(decoder, &integer) != SyntaErrorCode_Success) {
synta_decoder_free(decoder); // Clean up before returning
return ERROR;
}
synta_decoder_free(decoder);
```
### Pitfall 4: Freeing Borrowed Data
```c
// WRONG
const uint8_t *data = synta_octet_string_data(octet_string);
free(data); // CRASH! This is borrowed data
// RIGHT
const uint8_t *data = synta_octet_string_data(octet_string);
// Just use it, don't free it
memcpy(buffer, data, synta_octet_string_len(octet_string));
```
### Pitfall 5: Using Consumed Encoder
```c
// WRONG
SyntaEncoder *encoder = synta_encoder_new(SyntaEncoding_Der);
synta_encode_integer_i64(encoder, 42);
SyntaByteArray output;
synta_encoder_finish(encoder, &output); // encoder is consumed
synta_encode_boolean(encoder, true); // CRASH! encoder was consumed
// RIGHT
SyntaEncoder *encoder = synta_encoder_new(SyntaEncoding_Der);
synta_encode_integer_i64(encoder, 42);
synta_encode_boolean(encoder, true); // Encode everything first
SyntaByteArray output;
synta_encoder_finish(encoder, &output); // Then finish
```
### Pitfall 6: Not Checking owned Flag
```c
// WRONG - may leak or double-free
SyntaByteArray array = {0};
synta_decode_utf8_string(decoder, &array);
synta_byte_array_free(&array); // Might be wrong if not owned
// RIGHT
SyntaByteArray array = {0};
synta_decode_utf8_string(decoder, &array);
if (array.owned) {
synta_byte_array_free(&array);
}
```
## RAII Wrappers for C++
C++ users can use RAII wrappers for automatic cleanup:
```cpp
#include <synta.h>
#include <memory>
// Custom deleters
struct DecoderDeleter {
void operator()(SyntaDecoder *p) const { synta_decoder_free(p); }
};
struct IntegerDeleter {
void operator()(SyntaInteger *p) const { synta_integer_free(p); }
};
struct OidDeleter {
void operator()(SyntaObjectIdentifier *p) const { synta_oid_free(p); }
};
struct OctetStringDeleter {
void operator()(SyntaOctetString *p) const { synta_octet_string_free(p); }
};
// Type aliases
using DecoderPtr = std::unique_ptr<SyntaDecoder, DecoderDeleter>;
using IntegerPtr = std::unique_ptr<SyntaInteger, IntegerDeleter>;
using OidPtr = std::unique_ptr<SyntaObjectIdentifier, OidDeleter>;
using OctetStringPtr = std::unique_ptr<SyntaOctetString, OctetStringDeleter>;
// RAII ByteArray wrapper
class ByteArray {
public:
ByteArray() : array_{nullptr, 0, 0} {}
~ByteArray() { if (array_.owned) synta_byte_array_free(&array_); }
ByteArray(const ByteArray&) = delete;
ByteArray& operator=(const ByteArray&) = delete;
ByteArray(ByteArray&& other) noexcept : array_(other.array_) {
other.array_ = {nullptr, 0, 0};
}
SyntaByteArray* get() { return &array_; }
const uint8_t* data() const { return array_.data; }
size_t size() const { return array_.len; }
private:
SyntaByteArray array_;
};
// Usage example
void decode_example(const uint8_t* data, size_t len) {
DecoderPtr decoder(synta_decoder_new(data, len, SyntaEncoding_Der));
if (!decoder) {
throw std::runtime_error("Failed to create decoder");
}
SyntaInteger* raw_integer = nullptr;
if (synta_decode_integer(decoder.get(), &raw_integer) != SyntaErrorCode_Success) {
throw std::runtime_error("Failed to decode integer");
}
IntegerPtr integer(raw_integer);
int64_t value;
if (synta_integer_to_i64(integer.get(), &value) != SyntaErrorCode_Success) {
throw std::runtime_error("Integer overflow");
}
std::cout << "Value: " << value << "\n";
// All resources automatically cleaned up
}
```
## Debugging
### Valgrind
Use Valgrind to detect memory leaks and invalid memory access:
```bash
valgrind --leak-check=full --show-leak-kinds=all ./your_program
```
Expected output for correct code:
```
HEAP SUMMARY:
in use at exit: 0 bytes in 0 blocks
total heap usage: N allocs, N frees, X bytes allocated
All heap blocks were freed -- no leaks are possible
```
### AddressSanitizer
Compile with AddressSanitizer for runtime checks:
```bash
gcc -fsanitize=address -fno-omit-frame-pointer -g myapp.c -lcsynta -o myapp
./myapp
```
Detects:
- Use-after-free
- Heap buffer overflow
- Stack buffer overflow
- Memory leaks
- Double-free
### Debug Checklist
When debugging memory issues:
1. [+]Check all `_new()` functions have corresponding `_free()` calls
2. [+]Verify error paths also clean up
3. [+]Confirm encoder is not used after `synta_encoder_finish()`
4. [+]Check that borrowed pointers are not freed
5. [+]Verify `SyntaByteArray.owned` flag is checked before freeing
6. [+]Ensure no double-frees
7. [+]Check that all decoders from `enter_*` are freed
### Memory Debugging Tools
```c
// Helper macro for debug builds
#ifdef DEBUG_MEMORY
#define ALLOC_DECODER(...) \
({ \
SyntaDecoder *p = synta_decoder_new(__VA_ARGS__); \
fprintf(stderr, "ALLOC decoder %p\n", (void*)p); \
p; \
})
#define FREE_DECODER(p) \
do { \
fprintf(stderr, "FREE decoder %p\n", (void*)p); \
synta_decoder_free(p); \
} while(0)
#else
#define ALLOC_DECODER synta_decoder_new
#define FREE_DECODER synta_decoder_free
#endif
```
## See Also
- [C API Reference](C_API.md)
- [C Examples](../synta-ffi/examples/c/)
- [C Integration Tests](../tests/c/test_memory.c) - Memory safety test suite