# Ownership and Lifetimes
Understanding memory ownership and lifetime management in the Synta C API is
essential for writing correct, leak-free programs.
## Key Principles
1. **Explicit ownership** — It is always clear who owns what.
2. **Free what you create** — If a function returns a pointer, you must free it
with the corresponding `_free` function.
3. **Do not free borrowed data** — Some functions return borrowed references.
4. **One free per allocation** — Never double-free.
## Ownership Categories
### 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()` |
| `synta_byte_array_alloc()` | `synta_byte_array_free()` |
| `synta_byte_array_clone()` | `synta_byte_array_free()` |
| `synta_certificate_parse_der()` | `synta_certificate_free()` |
| `synta_crl_parse_der()` / `_pem()` | `synta_crl_free()` |
| `synta_csr_parse_der()` / `_pem()` | `synta_csr_free()` |
| `synta_ocsp_parse_der()` | `synta_ocsp_free()` |
| `synta_pem_to_der()` | `synta_der_list_free()` |
| `synta_cms_content_info_parse_der()` | `synta_cms_content_info_free()` |
### Caller-Owned (You Provide Storage)
These functions write into caller-provided storage:
```c
/* Caller provides storage; no allocation */
bool value;
synta_decode_boolean(decoder, &value);
SyntaByteArray output;
synta_decode_utf8_string(decoder, &output);
/* Free only if output.owned != 0 */
SyntaTag tag;
synta_decoder_peek_tag(decoder, &tag);
/* No cleanup needed; SyntaTag is a plain struct */
```
### Borrowed References (Do Not Free)
These functions return pointers to data owned by another object:
```c
/* Borrowed from SyntaOctetString — do NOT free */
const uint8_t *data = synta_octet_string_data(octet_string);
/* Borrowed from SyntaObjectIdentifier — do NOT free */
const uint32_t *components;
size_t len;
synta_oid_components(oid, &components, &len);
/* Static string — do NOT free */
const char *msg = synta_error_message(err);
```
Certificate getter functions return borrowed `SyntaByteArray` values
(`owned == 0`) pointing directly into the original DER buffer:
```c
SyntaByteArray subject = {0};
synta_certificate_get_subject_der(cert, &subject);
/* subject.owned == 0: valid while cert is alive; do NOT free */
```
### Consumed Objects
Some functions consume their argument:
```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);
```
## SyntaByteArray: owned Flag
`SyntaByteArray.owned` is a `uint32_t`:
- `0` = borrowed; do not free.
- Non-zero = owned allocation; caller must call `synta_byte_array_free()`.
Always check `owned` before freeing:
```c
SyntaByteArray array = {0};
synta_decode_utf8_string(decoder, &array);
if (array.owned != 0) {
synta_byte_array_free(&array);
}
```
`synta_byte_array_free()` already checks `owned` internally, so calling it on
a borrowed array is safe (it does nothing). However, explicitly checking
`owned` makes the intent clear in application code.
## Common Pitfalls
### 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 */
```
### 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 */
```
### Memory Leak on Error Path
```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 */
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);
```
### Freeing Borrowed Data
```c
/* WRONG */
const uint8_t *data = synta_octet_string_data(octet_string);
free(data); /* CRASH: borrowed data */
/* RIGHT */
const uint8_t *data = synta_octet_string_data(octet_string);
memcpy(buffer, data, synta_octet_string_len(octet_string)); /* just copy it */
```
### Using a Consumed Encoder
```c
/* WRONG */
SyntaEncoder *encoder = synta_encoder_new(SyntaEncoding_Der);
SyntaByteArray output;
synta_encoder_finish(encoder, &output); /* encoder consumed */
synta_encode_boolean(encoder, true); /* CRASH: encoder was consumed */
/* RIGHT */
SyntaEncoder *encoder = synta_encoder_new(SyntaEncoding_Der);
synta_encode_boolean(encoder, true); /* encode first */
SyntaByteArray output;
synta_encoder_finish(encoder, &output); /* then finish */
```
## RAII Wrappers for C++
C++ users can use `std::unique_ptr` with custom deleters for automatic cleanup:
```cpp
#include <synta.h>
#include <memory>
struct DecoderDeleter {
void operator()(SyntaDecoder *p) const { synta_decoder_free(p); }
};
struct IntegerDeleter {
void operator()(SyntaInteger *p) const { synta_integer_free(p); }
};
using DecoderPtr = std::unique_ptr<SyntaDecoder, DecoderDeleter>;
using IntegerPtr = std::unique_ptr<SyntaInteger, IntegerDeleter>;
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 by destructors */
}
```
## Debugging
### Valgrind
```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
All heap blocks were freed -- no leaks are possible
```
### AddressSanitizer
```bash
gcc -fsanitize=address -fno-omit-frame-pointer -g myapp.c -lcsynta -o myapp
./myapp
```
### Debug Checklist
1. Check all `_new()` and `parse_*()` 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 (`owned == 0`) are not freed.
5. Verify `SyntaByteArray.owned` flag is checked before freeing.
6. Ensure no double-frees.
7. Check that all child decoders from `enter_*` are freed.