/* $Id$ $Revision$ */
/* vim:set shiftwidth=4 ts=8: */

/*************************************************************************
 * Copyright (c) 2011 AT&T Intellectual Property 
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors: See CVS logs. Details at http://www.graphviz.org/
 *************************************************************************/

#include "config.h"
#include <stdint.h>
#ifdef HAVE_INTPTR_T
#define INT2PTR(t,v) ((t)(intptr_t)(v))
#else
#define INT2PTR(t,v) ((t)(v))
#endif

#include	"vmhdr.h"

/*	Method to help with debugging. This does rigorous checks on
**	addresses and arena integrity.
**
**	Written by Kiem-Phong Vo, kpv@research.att.com, 01/16/94.
*/

/* structure to keep track of file names */
typedef struct _dbfile_s Dbfile_t;
struct _dbfile_s {
    Dbfile_t *next;
    char file[1];
};
static Dbfile_t *Dbfile;

/* global watch list */
#define S_WATCH	32
static int Dbnwatch;
static void *Dbwatch[S_WATCH];

/* types of warnings reported by dbwarn() */
#define	DB_CHECK	0
#define DB_ALLOC	1
#define DB_FREE		2
#define DB_RESIZE	3
#define DB_WATCH	4
#define DB_RESIZED	5

static int Dbinit = 0;
#define DBINIT()	(Dbinit ? 0 : (dbinit(), Dbinit=1) )
static void dbinit(void)
{
    int fd;
    if ((fd = vmtrace(-1)) >= 0)
	vmtrace(fd);
}

/* just an entry point to make it easy to set break point */
static void vmdbwarn(Vmalloc_t * vm, char *mesg, int n)
{
    reg Vmdata_t *vd = vm->data;

    write(2, mesg, n);
    if (vd->mode & VM_DBABORT)
	abort();
}

/* issue a warning of some type */
/**
 * @param vm region holding the block
 * @param data data block
 * @param where byte that was corrupted
 * @param file file where call originates
 * @param line line number of call
 * @param type operation being done
 */
static void dbwarn(Vmalloc_t * vm, void * data, int where, char *file,
		   int line, int type)
{
    char buf[1024], *bufp, *endbuf, *s;
#define SLOP	64		/* enough for a message and an int */

    DBINIT();

    bufp = buf;
    endbuf = buf + sizeof(buf);

    if (type == DB_ALLOC)
	bufp = (*_Vmstrcpy) (bufp, "alloc error", ':');
    else if (type == DB_FREE)
	bufp = (*_Vmstrcpy) (bufp, "free error", ':');
    else if (type == DB_RESIZE)
	bufp = (*_Vmstrcpy) (bufp, "resize error", ':');
    else if (type == DB_CHECK)
	bufp = (*_Vmstrcpy) (bufp, "corrupted data", ':');
    else if (type == DB_WATCH)
	bufp = (*_Vmstrcpy) (bufp, "alert", ':');

    /* region info */
    bufp = (*_Vmstrcpy) (bufp, "region", '=');
    bufp = (*_Vmstrcpy) (bufp, (*_Vmitoa) (VLONG(vm), 0), ':');

    if (data) {
	bufp = (*_Vmstrcpy) (bufp, "block", '=');
	bufp = (*_Vmstrcpy) (bufp, (*_Vmitoa) (VLONG(data), 0), ':');
    }

    if (!data) {
	if (where == DB_ALLOC)
	    bufp = (*_Vmstrcpy) (bufp, "can't get memory", ':');
	else
	    bufp = (*_Vmstrcpy) (bufp, "region is locked", ':');
    } else if (type == DB_FREE || type == DB_RESIZE) {
	if (where == 0)
	    bufp = (*_Vmstrcpy) (bufp, "unallocated block", ':');
	else
	    bufp = (*_Vmstrcpy) (bufp, "already freed", ':');
    } else if (type == DB_WATCH) {
	bufp = (*_Vmstrcpy) (bufp, "size", '=');
	bufp = (*_Vmstrcpy) (bufp, (*_Vmitoa) (DBSIZE(data), -1), ':');
	if (where == DB_ALLOC)
	    bufp = (*_Vmstrcpy) (bufp, "just allocated", ':');
	else if (where == DB_FREE)
	    bufp = (*_Vmstrcpy) (bufp, "being freed", ':');
	else if (where == DB_RESIZE)
	    bufp = (*_Vmstrcpy) (bufp, "being resized", ':');
	else if (where == DB_RESIZED)
	    bufp = (*_Vmstrcpy) (bufp, "just resized", ':');
    } else if (type == DB_CHECK) {
	bufp = (*_Vmstrcpy) (bufp, "bad byte at", '=');
	bufp =
	    (*_Vmstrcpy) (bufp,
			  (*_Vmitoa) (VLONG(INT2PTR(char *, where)), -1),
			  ':');
	if ((s = DBFILE(data)) && (bufp + strlen(s) + SLOP) < endbuf) {
	    bufp = (*_Vmstrcpy) (bufp, "allocated at", '=');
	    bufp = (*_Vmstrcpy) (bufp, s, ',');
	    bufp =
		(*_Vmstrcpy) (bufp,
			      (*_Vmitoa) (VLONG
					  (INT2PTR(char *, DBLINE(data))),
					  -1), ':');
	}
    }

    /* location where offending call originates from */
    if (file && file[0] && line > 0
	&& (bufp + strlen(file) + SLOP) < endbuf) {
	bufp = (*_Vmstrcpy) (bufp, "detected at", '=');
	bufp = (*_Vmstrcpy) (bufp, file, ',');
	bufp =
	    (*_Vmstrcpy) (bufp,
			  (*_Vmitoa) (VLONG(INT2PTR(char *, where)), -1),
			  ':');
    }

    *bufp++ = '\n';
    *bufp = '\0';

    vmdbwarn(vm, buf, (bufp - buf));
}

/* check for watched address and issue warnings */
static void dbwatch(Vmalloc_t * vm, void * data, char *file, int line,
		    int type)
{
    reg int n;

    for (n = Dbnwatch; n >= 0; --n) {
	if (Dbwatch[n] == data) {
	    dbwarn(vm, data, type, file, line, DB_WATCH);
	    return;
	}
    }
}

/* record information about the block */
/**
 * @param data real address not the one from Vmbest
 * @param size the actual requested size
 * @param file file where the request came from
 * @param line and line number
 */
static void dbsetinfo(Vmuchar_t * data, size_t size, char *file, int line)
{
    reg Vmuchar_t *begp, *endp;
    reg Dbfile_t *last, *db;

    DBINIT();

    /* find the file structure */
    if (!file || !file[0])
	db = NIL(Dbfile_t *);
    else {
	for (last = NIL(Dbfile_t *), db = Dbfile; db;
	     last = db, db = db->next)
	    if (strcmp(db->file, file) == 0)
		break;
	if (!db) {
	    db = (Dbfile_t *) vmalloc(Vmheap,
				      sizeof(Dbfile_t) + strlen(file));
	    if (db) {
		(*_Vmstrcpy) (db->file, file, 0);
		db->next = Dbfile;
		Dbfile = db->next;
	    }
	} else if (last) {	/* move-to-front heuristic */
	    last->next = db->next;
	    db->next = Dbfile;
	    Dbfile = db->next;
	}
    }

    DBSETFL(data, (db ? db->file : NIL(char *)), line);
    DBSIZE(data) = size;
    DBSEG(data) = SEG(DBBLOCK(data));

    DBHEAD(data, begp, endp);
    while (begp < endp)
	*begp++ = DB_MAGIC;
    DBTAIL(data, begp, endp);
    while (begp < endp)
	*begp++ = DB_MAGIC;
}

/* Check to see if an address is in some data block of a region.
** This returns -(offset+1) if block is already freed, +(offset+1)
** if block is live, 0 if no match.
*/
static long dbaddr(Vmalloc_t * vm, void * addr)
{
    reg Block_t *b = NULL, *endb = NULL;
    reg Seg_t *seg;
    reg Vmuchar_t *data;
    reg long offset = -1L;
    reg Vmdata_t *vd = vm->data;
    reg int local;

    GETLOCAL(vd, local);
    if (ISLOCK(vd, local) || !addr)
	return -1L;
    SETLOCK(vd, local);

    for (seg = vd->seg; seg; seg = seg->next) {
	b = SEGBLOCK(seg);
	endb = (Block_t *) (seg->baddr - sizeof(Head_t));
	if ((Vmuchar_t *) addr > (Vmuchar_t *) b &&
	    (Vmuchar_t *) addr < (Vmuchar_t *) endb)
	    break;
    }
    if (!seg)
	goto done;

    if (local) {		/* must be vmfree or vmresize checking address */
	if (DBSEG(addr) == seg) {
	    b = DBBLOCK(addr);
	    if (ISBUSY(SIZE(b)) && !ISJUNK(SIZE(b)))
		offset = 0;
	    else
		offset = -2L;
	}
	goto done;
    }

    while (b < endb) {
	data = (Vmuchar_t *) DATA(b);
	if ((Vmuchar_t *) addr >= data
	    && (Vmuchar_t *) addr < data + SIZE(b)) {
	    if (ISBUSY(SIZE(b)) && !ISJUNK(SIZE(b))) {
		data = DB2DEBUG(data);
		if ((Vmuchar_t *) addr >= data &&
		    (Vmuchar_t *) addr < data + DBSIZE(data))
		    offset = (Vmuchar_t *) addr - data;
	    }
	    goto done;
	}

	b = (Block_t *) ((Vmuchar_t *) DATA(b) + (SIZE(b) & ~BITS));
    }

  done:
    CLRLOCK(vd, local);
    return offset;
}


static long dbsize(Vmalloc_t * vm, void * addr)
{
    reg Block_t *b, *endb;
    reg Seg_t *seg;
    reg long size;
    reg Vmdata_t *vd = vm->data;

    if (ISLOCK(vd, 0))
	return -1L;
    SETLOCK(vd, 0);

    size = -1L;
    for (seg = vd->seg; seg; seg = seg->next) {
	b = SEGBLOCK(seg);
	endb = (Block_t *) (seg->baddr - sizeof(Head_t));
	if ((Vmuchar_t *) addr <= (Vmuchar_t *) b ||
	    (Vmuchar_t *) addr >= (Vmuchar_t *) endb)
	    continue;
	while (b < endb) {
	    if (addr == (void *) DB2DEBUG(DATA(b))) {
		if (ISBUSY(SIZE(b)) && !ISJUNK(SIZE(b)))
		    size = (long) DBSIZE(addr);
		goto done;
	    }

	    b = (Block_t *) ((Vmuchar_t *) DATA(b) + (SIZE(b) & ~BITS));
	}
    }
  done:
    CLRLOCK(vd, 0);
    return size;
}

static void *dballoc(Vmalloc_t * vm, size_t size)
{
    reg size_t s;
    reg Vmuchar_t *data;
    reg char *file;
    reg int line;
    reg Vmdata_t *vd = vm->data;

    VMFILELINE(vm, file, line);

    if (ISLOCK(vd, 0)) {
	dbwarn(vm, NIL(Vmuchar_t *), 0, file, line, DB_ALLOC);
	return NIL(void *);
    }
    SETLOCK(vd, 0);

    if (vd->mode & VM_DBCHECK)
	vmdbcheck(vm);

    s = ROUND(size, ALIGN) + DB_EXTRA;
    if (s < sizeof(Body_t))	/* no tiny blocks during Vmdebug */
	s = sizeof(Body_t);

    if (!(data = (Vmuchar_t *) KPVALLOC(vm, s, (*(Vmbest->allocf))))) {
	dbwarn(vm, NIL(Vmuchar_t *), DB_ALLOC, file, line, DB_ALLOC);
	goto done;
    }

    data = DB2DEBUG(data);
    dbsetinfo(data, size, file, line);

    if ((vd->mode & VM_TRACE) && _Vmtrace) {
	vm->file = file;
	vm->line = line;
	(*_Vmtrace) (vm, NIL(Vmuchar_t *), data, size, 0);
    }

    if (Dbnwatch > 0)
	dbwatch(vm, data, file, line, DB_ALLOC);

  done:
    CLRLOCK(vd, 0);
    return (void *) data;
}


static int dbfree(Vmalloc_t * vm, void * data)
{
    char *file;
    int line;
    reg long offset;
    reg int *ip, *endip;
    reg Vmdata_t *vd = vm->data;

    VMFILELINE(vm, file, line);

    if (!data)
	return 0;

    if (ISLOCK(vd, 0)) {
	dbwarn(vm, NIL(Vmuchar_t *), 0, file, line, DB_FREE);
	return -1;
    }
    SETLOCK(vd, 0);

    if (vd->mode & VM_DBCHECK)
	vmdbcheck(vm);

    if ((offset = KPVADDR(vm, data, dbaddr)) != 0) {
	if (vm->disc->exceptf)
	    (void) (*vm->disc->exceptf) (vm, VM_BADADDR, data, vm->disc);
	dbwarn(vm, (Vmuchar_t *) data, offset == -1L ? 0 : 1, file, line,
	       DB_FREE);
	CLRLOCK(vd, 0);
	return -1;
    }

    if (Dbnwatch > 0)
	dbwatch(vm, data, file, line, DB_FREE);

    if ((vd->mode & VM_TRACE) && _Vmtrace) {
	vm->file = file;
	vm->line = line;
	(*_Vmtrace) (vm, (Vmuchar_t *) data, NIL(Vmuchar_t *),
		     DBSIZE(data), 0);
    }

    /* clear free space */
    ip = (int *) data;
    endip = ip + (DBSIZE(data) + sizeof(int) - 1) / sizeof(int);
    while (ip < endip)
	*ip++ = 0;

    CLRLOCK(vd, 0);
    return (*(Vmbest->freef)) (vm, (void *) DB2BEST(data));
}

/*	Resizing an existing block */
/**
 * @param vm region allocating from
 * @param addr old block of data
 * @param size new size
 * @param type !=0 for movable, >0 for copy
 */
static void *dbresize(Vmalloc_t * vm, void * addr, reg size_t size,
			int type)
{
    reg Vmuchar_t *data;
    reg size_t s, oldsize;
    reg long offset;
    char *file, *oldfile;
    int line, oldline;
    reg Vmdata_t *vd = vm->data;

    if (!addr) {
	oldsize = 0;
	data = (Vmuchar_t *) dballoc(vm, size);
	goto done;
    }
    if (size == 0) {
	(void) dbfree(vm, addr);
	return NIL(void *);
    }

    VMFILELINE(vm, file, line);

    if (ISLOCK(vd, 0)) {
	dbwarn(vm, NIL(Vmuchar_t *), 0, file, line, DB_RESIZE);
	return NIL(void *);
    }
    SETLOCK(vd, 0);

    if (vd->mode & VM_DBCHECK)
	vmdbcheck(vm);

    if ((offset = KPVADDR(vm, addr, dbaddr)) != 0) {
	if (vm->disc->exceptf)
	    (void) (*vm->disc->exceptf) (vm, VM_BADADDR, addr, vm->disc);
	dbwarn(vm, (Vmuchar_t *) addr, offset == -1L ? 0 : 1, file, line,
	       DB_RESIZE);
	CLRLOCK(vd, 0);
	return NIL(void *);
    }

    if (Dbnwatch > 0)
	dbwatch(vm, addr, file, line, DB_RESIZE);

    /* Vmbest data block */
    data = DB2BEST(addr);
    oldsize = DBSIZE(addr);
    oldfile = DBFILE(addr);
    oldline = DBLINE(addr);

    /* do the resize */
    s = ROUND(size, ALIGN) + DB_EXTRA;
    if (s < sizeof(Body_t))
	s = sizeof(Body_t);
    data = (Vmuchar_t *) KPVRESIZE(vm, (void *) data, s,
				   (type & ~VM_RSZERO),
				   (*(Vmbest->resizef)));
    if (!data) {		/* failed, reset data for old block */
	dbwarn(vm, NIL(Vmuchar_t *), DB_ALLOC, file, line, DB_RESIZE);
	dbsetinfo((Vmuchar_t *) addr, oldsize, oldfile, oldline);
    } else {
	data = DB2DEBUG(data);
	dbsetinfo(data, size, file, line);

	if ((vd->mode & VM_TRACE) && _Vmtrace) {
	    vm->file = file;
	    vm->line = line;
	    (*_Vmtrace) (vm, (Vmuchar_t *) addr, data, size, 0);
	}
	if (Dbnwatch > 0)
	    dbwatch(vm, data, file, line, DB_RESIZED);
    }

    CLRLOCK(vd, 0);

  done:if (data && (type & VM_RSZERO) && size > oldsize) {
	reg Vmuchar_t *d = data + oldsize, *ed = data + size;
	do {
	    *d++ = 0;
	} while (d < ed);
    }
    return (void *) data;
}

/* compact any residual free space */
static int dbcompact(Vmalloc_t * vm)
{
    return (*(Vmbest->compactf)) (vm);
}

/* check for memory overwrites over all live blocks */
int vmdbcheck(Vmalloc_t * vm)
{
    reg Block_t *b, *endb;
    reg Seg_t *seg;
    int rv;
    reg Vmdata_t *vd = vm->data;

    if (!(vd->mode & VM_MTDEBUG))
	return -1;

    rv = 0;
    for (seg = vd->seg; seg; seg = seg->next) {
	b = SEGBLOCK(seg);
	endb = (Block_t *) (seg->baddr - sizeof(Head_t));
	while (b < endb) {
	    reg Vmuchar_t *data, *begp, *endp;

	    if (ISJUNK(SIZE(b)) || !ISBUSY(SIZE(b)))
		goto next;

	    data = DB2DEBUG(DATA(b));
	    if (DBISBAD(data)) {	/* seen this before */
		rv += 1;
		goto next;
	    }

	    DBHEAD(data, begp, endp);
	    for (; begp < endp; ++begp)
		if (*begp != DB_MAGIC)
		    goto set_bad;

	    DBTAIL(data, begp, endp);
	    for (; begp < endp; ++begp) {
		if (*begp == DB_MAGIC)
		    continue;
	      set_bad:
		dbwarn(vm, data, begp - data, NIL(char *), 0, DB_CHECK);
		DBSETBAD(data);
		rv += 1;
		goto next;
	    }

	  next:b = (Block_t *) ((Vmuchar_t *) DATA(b) +
			     (SIZE(b) & ~BITS));
	}
    }

    return rv;
}

/* set/delete an address to watch */
/**
 * set/delete an address to watch
 *
 * @param addr address to insert
 */
void *vmdbwatch(void * addr)
{
    reg int n;
    reg void *out;

    out = NIL(void *);
    if (!addr)
	Dbnwatch = 0;
    else {
	for (n = Dbnwatch - 1; n >= 0; --n)
	    if (Dbwatch[n] == addr)
		break;
	if (n < 0) {		/* insert */
	    if (Dbnwatch == S_WATCH) {	/* delete left-most */
		out = Dbwatch[0];
		Dbnwatch -= 1;
		for (n = 0; n < Dbnwatch; ++n)
		    Dbwatch[n] = Dbwatch[n + 1];
	    }
	    Dbwatch[Dbnwatch] = addr;
	    Dbnwatch += 1;
	}
    }
    return out;
}

static void *dbalign(Vmalloc_t * vm, size_t size, size_t align)
{
    reg Vmuchar_t *data;
    reg size_t s;
    reg char *file;
    reg int line;
    reg Vmdata_t *vd = vm->data;

    VMFILELINE(vm, file, line);

    if (size <= 0 || align <= 0)
	return NIL(void *);

    if (!(vd->mode & VM_TRUST)) {
	if (ISLOCK(vd, 0))
	    return NIL(void *);
	SETLOCK(vd, 0);
    }

    if ((s = ROUND(size, ALIGN) + DB_EXTRA) < sizeof(Body_t))
	s = sizeof(Body_t);

    if (!
	(data = (Vmuchar_t *) KPVALIGN(vm, s, align, (*(Vmbest->alignf)))))
	goto done;

    data += DB_HEAD;
    dbsetinfo(data, size, file, line);

    if ((vd->mode & VM_TRACE) && _Vmtrace) {
	vm->file = file;
	vm->line = line;
	(*_Vmtrace) (vm, NIL(Vmuchar_t *), data, size, align);
    }

  done:
    CLRLOCK(vd, 0);
    return (void *) data;
}

static Vmethod_t _Vmdebug = {
    dballoc,
    dbresize,
    dbfree,
    dbaddr,
    dbsize,
    dbcompact,
    dbalign,
    VM_MTDEBUG
};

Vmethod_t* Vmdebug = &_Vmdebug;
