// Simplified version of mempool.c, that is more oriented towards
// checking that the description of invalid addresses is correct.

#include <stdio.h>
#include <unistd.h>
#include "tests/sys_mman.h"
#include <assert.h>
#include <stdlib.h>

#include "../memcheck.h"

#define SUPERBLOCK_SIZE 100000
#define REDZONE_SIZE 8

typedef struct _level_list
{
   struct _level_list *next;
   char *where;
   // Padding ensures the struct is the same size on 32-bit and 64-bit
   // machines.
   char padding[16 - 2*sizeof(char*)];
} level_list;

typedef struct _pool {
   char *mem;
   char *where; 
   level_list *levels;
   int size, left;
   // Padding ensures the struct is the same size on 32-bit and 64-bit
   // machines.
   char padding[24 - 3*sizeof(char*)];
} pool;

pool *make_pool( int use_mmap )
{
   pool *p;

   if (use_mmap) {
      p = (pool *)mmap(0, sizeof(pool), PROT_READ|PROT_WRITE|PROT_EXEC,
                       MAP_PRIVATE|MAP_ANONYMOUS, -1, 0);
      p->where = p->mem = (char *)mmap(NULL, SUPERBLOCK_SIZE,
                                       PROT_READ|PROT_WRITE|PROT_EXEC,
                                       MAP_PRIVATE|MAP_ANONYMOUS, -1, 0);
   } else {
      p = (pool *)malloc(sizeof(pool));
      p->where = p->mem = (char *)malloc(SUPERBLOCK_SIZE);
   }

   p->size = p->left = SUPERBLOCK_SIZE;
   p->levels = NULL;
   VALGRIND_MAKE_MEM_NOACCESS(p->where, SUPERBLOCK_SIZE);
   return p;
}

void push(pool *p, int use_mmap )
{
   level_list *l;

   if (use_mmap)
      l = (level_list *)mmap(0, sizeof(level_list),
                             PROT_READ|PROT_WRITE|PROT_EXEC,
                             MAP_PRIVATE|MAP_ANONYMOUS, -1, 0);
   else
      l = (level_list *)malloc(sizeof(level_list));

   l->next = p->levels;
   l->where = p->where;
   VALGRIND_CREATE_MEMPOOL(l->where, REDZONE_SIZE, 0);
   p->levels = l;
}

void pop(pool *p, int use_mmap)
{
   level_list *l = p->levels;
   p->levels = l->next;
   VALGRIND_DESTROY_MEMPOOL(l->where);
   VALGRIND_MAKE_MEM_NOACCESS(l->where, p->where-l->where);
   p->where = l->where;
   if (use_mmap)
      munmap(l, sizeof(level_list));
   else
      free(l);
}

void destroy_pool(pool *p, int use_mmap)
{
   level_list *l = p->levels;

   while(l) {
      pop(p, use_mmap);
   }
   if (use_mmap) {
      munmap(p->mem, SUPERBLOCK_SIZE);
      munmap(p, sizeof(pool));
   } else {
      free(p->mem);
      free(p);
   }
}

char *allocate(pool *p, int size)
{
   char *where;
   p->left -= size + (REDZONE_SIZE*2);
   where = p->where + REDZONE_SIZE;
   p->where += size + (REDZONE_SIZE*2);
   VALGRIND_MEMPOOL_ALLOC(p->levels->where, where, size);
   return where;
}

//-------------------------------------------------------------------------
// Rest
//-------------------------------------------------------------------------

void test(void)
{
   char *x1, *x2;
   char res = 0;

   // p1 is a malloc-backed pool
   pool *p1 = make_pool(0);

   // p2 is a mmap-backed pool
   pool *p2 = make_pool(1);

   push(p1, 0);
   push(p2, 1);

   x1 = allocate(p1, 10);
   x2 = allocate(p2, 20);

   fprintf(stderr,
           "\n------ out of range reads in malloc-backed pool ------\n\n");
   res += x1[-1];
   res += x1[10];

   fprintf(stderr,
           "\n------ out of range reads in mmap-backed pool ------\n\n");
   res += x2[-1]; // invalid
   res += x2[20]; // invalid

   fprintf(stderr,
           "\n------ read free in malloc-backed pool ------\n\n");
   VALGRIND_MEMPOOL_FREE(p1, x1);
   res += x1[5];

   fprintf(stderr,
           "\n------ read free in mmap-backed pool ------\n\n");
   VALGRIND_MEMPOOL_FREE(p2, x2);
   res += x2[11];

   fprintf(stderr,
           "\n------ double free in malloc-backed pool ------\n\n");
   VALGRIND_MEMPOOL_FREE(p1, x1);

   fprintf(stderr,
           "\n------ double free in mmap-backed pool ------\n\n");
   VALGRIND_MEMPOOL_FREE(p2, x2);

   {
      // test that redzone are still protected even if the user forgets
      // to mark the superblock noaccess.
      char superblock[100];

      VALGRIND_CREATE_MEMPOOL(superblock, REDZONE_SIZE, 0);
      // User should mark the superblock no access to benefit
      // from full Valgrind memcheck protection.
      // VALGRIND_MEMPOOL_ALLOC will however still ensure the
      // redzones are protected.
      VALGRIND_MEMPOOL_ALLOC(superblock, superblock+30, 10);

      res += superblock[30]; // valid
      res += superblock[39]; // valid

      fprintf(stderr,
              "\n------ 2 invalid access in 'no no-access superblock' ---\n\n");
      res += superblock[29]; // invalid
      res += superblock[40]; // invalid

      VALGRIND_DESTROY_MEMPOOL(superblock);
   }
   // claim res is used, so gcc can't nuke this all
   __asm__ __volatile__("" : : "r"(res));

   fprintf(stderr,
           "\n------ done ------\n\n");
   pop(p1, 0);
   pop(p2, 1);
   destroy_pool(p1, 0);
   destroy_pool(p2, 1);
}

int main(void)
{
   test();
   return 0;
}