/* watch.c - watchpoint functions for malloc */

/* Copyright (C) 2001-2003, 2022 Free Software Foundation, Inc.

   This file is part of GNU Bash, the Bourne Again SHell.

   Bash is free software: you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation, either version 3 of the License, or
   (at your option) any later version.

   Bash is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with Bash.  If not, see <http://www.gnu.org/licenses/>.
*/

#ifdef HAVE_CONFIG_H
#  include <config.h>
#endif

#include <stdio.h>

#include "imalloc.h"

#ifdef MALLOC_WATCH
#include "watch.h"

#define WATCH_MAX	32

int		_malloc_nwatch;
static PTR_T	_malloc_watch_list[WATCH_MAX];

static void
watch_warn (PTR_T addr, const char *file, int line, int type, unsigned long data)
{
  char *tag;

  if (type == W_ALLOC)
    tag = "allocated";
  else if (type == W_FREE)
    tag = "freed";
  else if (type == W_REALLOC)
    tag = "requesting resize";
  else if (type == W_RESIZED)
    tag = "just resized";
  else
    tag = "bug: unknown operation";

  fprintf (stderr, "malloc: watch alert: %p %s ", addr, tag);
  if (data != (unsigned long)-1)
    fprintf (stderr, "(size %lu) ", data);
  fprintf (stderr, "from '%s:%d'\n", file ? file : "unknown", line);
}

void
_malloc_ckwatch (PTR_T addr, const char *file, int line, int type, unsigned long data)
{
  register int i;

  for (i = _malloc_nwatch - 1; i >= 0; i--)
    {
      if (_malloc_watch_list[i] == addr)
	{
	  watch_warn (addr, file, line, type, data);
	  return;
	}
    }
}
#endif /* MALLOC_WATCH */

PTR_T
malloc_watch (PTR_T addr)
{
  register int i;
  PTR_T ret;

  if (addr == 0)
    return addr;
  ret = (PTR_T)0;

#ifdef MALLOC_WATCH
  for (i = _malloc_nwatch - 1; i >= 0; i--)
    {
      if (_malloc_watch_list[i] == addr)
        break;
    }
  if (i < 0)
    {
      if (_malloc_nwatch == WATCH_MAX)	/* full, take out first */
	{
	  ret = _malloc_watch_list[0];
	  _malloc_nwatch--;
	  for (i = 0; i < _malloc_nwatch; i++)
	    _malloc_watch_list[i] = _malloc_watch_list[i+1];
	}
      _malloc_watch_list[_malloc_nwatch++] = addr;
    }
#endif

  return ret;  
}

/* Remove a watchpoint set on ADDR.  If ADDR is NULL, remove all
   watchpoints.  Returns ADDR if everything went OK, NULL if ADDR was
   not being watched. */
PTR_T
malloc_unwatch (PTR_T addr)
{
#ifdef MALLOC_WATCH
  register int i;

  if (addr == 0)
    {
      for (i = 0; i < _malloc_nwatch; i++)
        _malloc_watch_list[i] = (PTR_T)0;
      _malloc_nwatch = 0;
      return ((PTR_T)0);
    }
  else
    {
      for (i = 0; i < _malloc_nwatch; i++)
	{
	  if (_malloc_watch_list[i] == addr)
	    break;
	}
      if (i == _malloc_nwatch)
        return ((PTR_T)0);		/* not found */
      /* shuffle everything from i+1 to end down 1 */
      _malloc_nwatch--;
      for ( ; i < _malloc_nwatch; i++)
        _malloc_watch_list[i] = _malloc_watch_list[i+1];
      return addr;
    }
#else
  return ((PTR_T)0);
#endif
}
