data-stack.c revision 8cf55b1c05d0d0a19c95ebbc8390ceb1d36da4a9
/* Copyright (c) 2002-2016 Dovecot authors, see the included COPYING file */
/* @UNSAFE: whole file */
#include "lib.h"
#include "data-stack.h"
#ifdef HAVE_GC_GC_H
# include <gc.h>
#endif
/* Initial stack size - this should be kept in a size that doesn't exceed
in a normal use to avoid extra malloc()ing. */
#ifdef DEBUG
#else
#endif
#ifdef DEBUG
#else
# define CLEAR_CHR 0
# define BLOCK_CANARY NULL
# define BLOCK_CANARY_CHECK(block) do { ; } while(0)
#endif
struct stack_block {
struct stack_block *next;
/* NULL or a poison value, just in case something accesses
the memory in front of an allocated area */
void *canary;
/* unsigned char data[]; */
};
#define STACK_BLOCK_DATA(block) \
((unsigned char *) (block) + SIZEOF_MEMBLOCK)
/* current_frame_block contains last t_push()ed frames. After that new
stack_frame_block is created and it's ->prev is set to
current_frame_block. */
#define BLOCK_FRAME_COUNT 32
struct stack_frame_block {
struct stack_frame_block *prev;
#ifdef DEBUG
const char *marker[BLOCK_FRAME_COUNT];
/* Fairly arbitrary profiling data */
unsigned long long alloc_bytes[BLOCK_FRAME_COUNT];
unsigned int alloc_count[BLOCK_FRAME_COUNT];
#endif
};
unsigned int data_stack_frame = 0;
static struct stack_frame_block *current_frame_block;
static struct stack_frame_block *unused_frame_blocks;
static struct stack_block *last_buffer_block;
static size_t last_buffer_size;
#ifdef DEBUG
static bool clean_after_pop = TRUE;
#else
static bool clean_after_pop = FALSE;
#endif
static union {
struct stack_block block;
unsigned char data[512];
static inline
{
}
{
if (last_buffer_block != NULL) {
#ifdef DEBUG
unsigned char *last_alloc_end, *p, *pend;
#endif
/* reset t_buffer_get() mark - not really needed but makes it
easier to notice if t_malloc()/t_push()/t_pop() is called
between t_buffer_get() and t_buffer_alloc().
do this before we get to i_panic() to avoid recursive
panics. */
#ifdef DEBUG
while (p < pend)
if (*p++ != CLEAR_CHR)
i_panic("t_buffer_get(): buffer overflow");
if (!preserve_data) {
p = last_alloc_end;
}
#endif
}
}
{
struct stack_frame_block *frame_block;
frame_pos++;
if (frame_pos == BLOCK_FRAME_COUNT) {
/* frame block full */
if (unlikely(data_stack_frame == 0)) {
/* kludgy, but allow this before initialization */
frame_pos = 0;
}
frame_pos = 0;
if (unused_frame_blocks == NULL) {
/* allocate new block */
#ifndef USE_GC
#else
#endif
if (frame_block == NULL) {
"t_push(): Out of memory");
}
} else {
/* use existing unused frame_block */
}
}
/* mark our current position */
#ifdef DEBUG
#else
(void)marker; /* only used for debugging */
#endif
return data_stack_frame++;
}
unsigned int t_push_named(const char *format, ...)
{
#ifdef DEBUG
#else
(void)format; /* unused in non-DEBUG builds */
#endif
return ret;
}
{
struct stack_block *next;
/* free all the blocks, except if any of them is bigger than
unused_block, replace it */
if (clean_after_pop)
#ifndef USE_GC
#endif
} else {
#ifndef USE_GC
#endif
}
}
}
#ifdef DEBUG
static void t_pop_verify(void)
{
struct stack_block *block;
unsigned char *p;
p = STACK_BLOCK_DATA(block);
i_panic("data stack[%s]: saved alloc size broken",
i_panic("data stack[%s]: buffer overflow",
}
}
/* if we had used t_buffer_get(), the rest of the buffer
may not contain CLEAR_CHRs. but we've already checked all
the allocations, so there's no need to check them anyway. */
pos = 0;
}
}
#endif
unsigned int t_pop(void)
{
struct stack_frame_block *frame_block;
i_panic("t_pop() called with empty stack");
#ifdef DEBUG
t_pop_verify();
#endif
/* update the current block */
if (clean_after_pop) {
}
/* free unused blocks */
}
if (frame_pos > 0)
frame_pos--;
else {
/* frame block is now unused, add it to unused list */
}
return --data_stack_frame;
}
void t_pop_check(unsigned int *id)
{
i_panic("Leaked t_pop() call");
*id = 0;
}
{
struct stack_block *block;
#ifndef USE_GC
#else
#endif
if (outofmem) {
abort();
return &outofmem_area.block;
}
i_panic("data stack: Out of memory when allocating %"
}
#ifdef DEBUG
#endif
return block;
}
{
void *ret;
#ifdef DEBUG
#endif
if (unlikely(data_stack_frame == 0)) {
/* kludgy, but allow this before initialization */
}
/* allocate only aligned amount of memory so alignment comes
always properly */
#ifdef DEBUG
if(permanent) {
}
#endif
/* used for t_try_realloc() */
struct stack_block *block;
/* current block is full, see if we can use the unused_block */
unused_block = NULL;
} else {
#ifdef DEBUG
#endif
}
}
/* enough space in current block, use it */
if (permanent)
#ifdef DEBUG
/* warn after allocation, so if i_debug() wants to
allocate more memory we don't go to infinite loop */
"'%s' reaches %llu bytes from %u allocations.",
}
/* make sure the sentry contains CLEAR_CHRs. it might not if we
had used t_buffer_get(). */
/* we rely on errno not changing. it shouldn't. */
#endif
return ret;
}
{
}
{
void *mem;
return mem;
}
{
unsigned char *after_last_alloc;
/* see if we're trying to grow the memory we allocated last */
#ifdef DEBUG
#endif
/* yeah, see if we have space to grow */
#ifdef DEBUG
/* Only check one byte for over-run, that catches most
offenders who are likely to use t_try_realloc() */
#endif
/* just shrink the available size */
#ifdef DEBUG
/* All reallocs are permanent by definition
However, they don't count as a new allocation */
#endif
return TRUE;
}
}
return FALSE;
}
size_t t_get_bytes_available(void)
{
#ifndef DEBUG
#else
#endif
}
{
void *ret;
return ret;
}
{
void *new_buffer;
return buffer;
if (new_buffer != buffer)
return new_buffer;
}
{
/* we've already reserved the space, now we just mark it used */
}
void t_buffer_alloc_last_full(void)
{
if (last_buffer_block != NULL)
}
{
#ifndef DEBUG
#endif
}
void data_stack_init(void)
{
if (data_stack_frame > 0) {
/* already initialized (we did auto-initialization in
return;
}
data_stack_frame = 1;
last_buffer_size = 0;
(void)t_push("data_stack_init");
}
void data_stack_deinit(void)
{
(void)t_pop();
i_panic("Missing t_pop() call");
#ifndef USE_GC
while (unused_frame_blocks != NULL) {
}
#endif
unused_block = NULL;
}