summary refs log tree commit diff
path: root/Src
diff options
context:
space:
mode:
Diffstat (limited to 'Src')
-rw-r--r--Src/Zle/comp.h3
-rw-r--r--Src/Zle/compcore.c31
-rw-r--r--Src/Zle/compctl.c5
-rw-r--r--Src/Zle/complist.c20
-rw-r--r--Src/Zle/compresult.c79
-rw-r--r--Src/mem.c194
-rw-r--r--Src/zsh.h60
7 files changed, 386 insertions, 6 deletions
diff --git a/Src/Zle/comp.h b/Src/Zle/comp.h
index a8be74d03..34da2cabb 100644
--- a/Src/Zle/comp.h
+++ b/Src/Zle/comp.h
@@ -76,6 +76,9 @@ struct cmgroup {
     int totl;			/* total length */
     int shortest;		/* length of shortest match */
     Cmgroup perm;		/* perm. alloced version of this group */
+#ifdef ZSH_HEAP_DEBUG
+    Heapid heap_id;
+#endif
 };
 
 
diff --git a/Src/Zle/compcore.c b/Src/Zle/compcore.c
index fa8b8c11f..9c6f0673a 100644
--- a/Src/Zle/compcore.c
+++ b/Src/Zle/compcore.c
@@ -405,6 +405,11 @@ do_completion(UNUSED(Hookdef dummy), Compldat dat)
 	} else if (nmatches == 1 || (nmatches > 1 && !diffmatches)) {
 	    /* Only one match. */
 	    Cmgroup m = amatches;
+#ifdef ZSH_HEAP_DEBUG
+	    if (memory_validate(m->heap_id)) {
+		HEAP_ERROR(m->heap_id);
+	    }
+#endif
 
 	    while (!m->mcount)
 		m = m->next;
@@ -509,6 +514,11 @@ after_complete(UNUSED(Hookdef dummy), int *dat)
 	int ret;
 
 	cdat.matches = amatches;
+#ifdef ZSH_HEAP_DEBUG
+	if (memory_validate(cdat.matches->heap_id)) {
+	    HEAP_ERROR(cdat.matches->heap_id);
+	}
+#endif
 	cdat.num = nmatches;
 	cdat.nmesg = nmessages;
 	cdat.cur = NULL;
@@ -987,6 +997,11 @@ makecomplist(char *s, int incmd, int lst)
 	    diffmatches = odm;
 	    validlist = 1;
 	    amatches = lastmatches;
+#ifdef ZSH_HEAP_DEBUG
+	    if (memory_validate(amatches->heap_id)) {
+		HEAP_ERROR(amatches->heap_id);
+	    }
+#endif
 	    lmatches = lastlmatches;
 	    if (pmatches) {
 		freematches(pmatches, 1);
@@ -2959,6 +2974,11 @@ begcmgroup(char *n, int flags)
 	Cmgroup p = amatches;
 
 	while (p) {
+#ifdef ZSH_HEAP_DEBUG
+	    if (memory_validate(p->heap_id)) {
+		HEAP_ERROR(p->heap_id);
+	    }
+#endif
 	    if (p->name &&
 		flags == (p->flags & (CGF_NOSORT|CGF_UNIQALL|CGF_UNIQCON)) &&
 		!strcmp(n, p->name)) {
@@ -2975,6 +2995,9 @@ begcmgroup(char *n, int flags)
 	}
     }
     mgroup = (Cmgroup) zhalloc(sizeof(struct cmgroup));
+#ifdef ZSH_HEAP_DEBUG
+    mgroup->heap_id = last_heap_id;
+#endif
     mgroup->name = dupstring(n);
     mgroup->lcount = mgroup->llcount = mgroup->mcount = mgroup->ecount = 
 	mgroup->ccount = 0;
@@ -3295,6 +3318,11 @@ permmatches(int last)
 	fi = 1;
     }
     while (g) {
+#ifdef ZSH_HEAP_DEBUG
+	if (memory_validate(g->heap_id)) {
+	    HEAP_ERROR(g->heap_id);
+	}
+#endif
 	if (fi != ofi || !g->perm || g->new) {
 	    if (fi)
 		/* We have no matches, try ignoring fignore. */
@@ -3323,6 +3351,9 @@ permmatches(int last)
 		diffmatches = 1;
 
 	    n = (Cmgroup) zshcalloc(sizeof(struct cmgroup));
+#ifdef ZSH_HEAP_DEBUG
+	    n->heap_id = HEAPID_PERMANENT;
+#endif
 
 	    if (g->perm) {
 		g->perm->next = NULL;
diff --git a/Src/Zle/compctl.c b/Src/Zle/compctl.c
index 873d9287a..170efff3f 100644
--- a/Src/Zle/compctl.c
+++ b/Src/Zle/compctl.c
@@ -1838,6 +1838,11 @@ ccmakehookfn(UNUSED(Hookdef dummy), struct ccmakedat *dat)
 	    diffmatches = odm;
 	    validlist = 1;
 	    amatches = lastmatches;
+#ifdef ZSH_HEAP_DEBUG
+	    if (memory_validate(amatches->heap_id)) {
+		HEAP_ERROR(amatches->heap_id);
+	    }
+#endif
 	    lmatches = lastlmatches;
 	    if (pmatches) {
 		freematches(pmatches, 1);
diff --git a/Src/Zle/complist.c b/Src/Zle/complist.c
index 6d0da448c..fdca7a99f 100644
--- a/Src/Zle/complist.c
+++ b/Src/Zle/complist.c
@@ -1363,6 +1363,11 @@ compprintlist(int showall)
     while (g) {
 	char **pp = g->ylist;
 
+#ifdef ZSH_HEAP_DEBUG
+	if (memory_validate(g->heap_id)) {
+	    HEAP_ERROR(g->heap_id);
+	}
+#endif
 	if ((e = g->expls)) {
 	    int l;
 
@@ -1952,6 +1957,11 @@ complistmatches(UNUSED(Hookdef dummy), Chdata dat)
     Cmgroup oamatches = amatches;
 
     amatches = dat->matches;
+#ifdef ZSH_HEAP_DEBUG
+    if (memory_validate(amatches->heap_id)) {
+	HEAP_ERROR(amatches->heap_id);
+    }
+#endif
 
     noselect = 0;
 
@@ -2640,6 +2650,11 @@ domenuselect(Hookdef dummy, Chdata dat)
 	    s->mlbeg = mlbeg;
 	    memcpy(&(s->info), &minfo, sizeof(struct menuinfo));
 	    s->amatches = amatches;
+#ifdef ZSH_HEAP_DEBUG
+	    if (memory_validate(amatches->heap_id)) {
+		HEAP_ERROR(amatches->heap_id);
+	    }
+#endif
 	    s->pmatches = pmatches;
 	    s->lastmatches = lastmatches;
 	    s->lastlmatches = lastlmatches;
@@ -2835,6 +2850,11 @@ domenuselect(Hookdef dummy, Chdata dat)
 		if (lastmatches)
 		    freematches(lastmatches, 0);
 		amatches = u->amatches;
+#ifdef ZSH_HEAP_DEBUG
+		if (memory_validate(amatches->heap_id)) {
+		    HEAP_ERROR(amatches->heap_id);
+		}
+#endif
 		pmatches = u->pmatches;
 		lastmatches = u->lastmatches;
 		lastlmatches = u->lastlmatches;
diff --git a/Src/Zle/compresult.c b/Src/Zle/compresult.c
index f2729a0fe..c0e5ff3d8 100644
--- a/Src/Zle/compresult.c
+++ b/Src/Zle/compresult.c
@@ -906,7 +906,14 @@ do_allmatches(UNUSED(int end))
 
     for (minfo.group = amatches;
 	 minfo.group && !(minfo.group)->mcount;
-	 minfo.group = (minfo.group)->next);
+	 minfo.group = (minfo.group)->next) {
+#ifdef ZSH_HEAP_DEBUG
+	if (memory_validate(minfo.group->heap_id)) {
+	    HEAP_ERROR(minfo.group->heap_id);
+	}
+#endif
+    }
+
 
     mc = (minfo.group)->matches;
 
@@ -1172,6 +1179,11 @@ do_single(Cmatch m)
 	struct chdata dat;
 
 	dat.matches = amatches;
+#ifdef ZSH_HEAP_DEBUG
+	if (memory_validate(dat.matches->heap_id)) {
+	    HEAP_ERROR(dat.matches->heap_id);
+	}
+#endif
 	dat.num = nmatches;
 	dat.cur = m;
 
@@ -1210,8 +1222,14 @@ do_menucmp(int lst)
     do {
 	if (!*++(minfo.cur)) {
 	    do {
-		if (!(minfo.group = (minfo.group)->next))
+		if (!(minfo.group = (minfo.group)->next)) {
 		    minfo.group = amatches;
+#ifdef ZSH_HEAP_DEBUG
+		    if (memory_validate(minfo.group->heap_id)) {
+			HEAP_ERROR(minfo.group->heap_id);
+		    }
+#endif
+		}
 	    } while (!(minfo.group)->mcount);
 	    minfo.cur = minfo.group->matches;
 	}
@@ -1291,12 +1309,18 @@ accept_last(void)
 	    Cmgroup g;
 	    Cmatch *m;
 
-	    for (g = amatches, m = NULL; g && (!m || !*m); g = g->next)
+	    for (g = amatches, m = NULL; g && (!m || !*m); g = g->next) {
+#ifdef ZSH_HEAP_DEBUG
+		if (memory_validate(g->heap_id)) {
+		    HEAP_ERROR(g->heap_id);
+		}
+#endif
 		for (m = g->matches; *m; m++)
 		    if (!hasbrpsfx(*m, minfo.prebr, minfo.postbr)) {
 			showinglist = -2;
 			break;
 		    }
+	    }
 	}
     }
     menuacc++;
@@ -1381,7 +1405,13 @@ do_ambig_menu(void)
 	insgnum = comp_mod(insgnum, lastpermgnum);
 	for (minfo.group = amatches;
 	     minfo.group && (minfo.group)->num != insgnum + 1;
-	     minfo.group = (minfo.group)->next);
+	     minfo.group = (minfo.group)->next) {
+#ifdef ZSH_HEAP_DEBUG
+	    if (memory_validate(minfo.group->heap_id)) {
+		HEAP_ERROR(minfo.group->heap_id);
+	    }
+#endif
+	}
 	if (!minfo.group || !(minfo.group)->mcount) {
 	    minfo.cur = NULL;
 	    minfo.asked = 0;
@@ -1393,8 +1423,14 @@ do_ambig_menu(void)
 	insmnum = comp_mod(insmnum, lastpermmnum);
 	for (minfo.group = amatches;
 	     minfo.group && (minfo.group)->mcount <= insmnum;
-	     minfo.group = (minfo.group)->next)
+	     minfo.group = (minfo.group)->next) {
 	    insmnum -= (minfo.group)->mcount;
+#ifdef ZSH_HEAP_DEBUG
+	    if (memory_validate(minfo.group->heap_id)) {
+		HEAP_ERROR(minfo.group->heap_id);
+	    }
+#endif
+	}
 	if (!minfo.group) {
 	    minfo.cur = NULL;
 	    minfo.asked = 0;
@@ -1483,6 +1519,11 @@ calclist(int showall)
 	int nl = 0, l, glong = 1, gshort = zterm_columns, ndisp = 0, totl = 0;
         int hasf = 0;
 
+#ifdef ZSH_HEAP_DEBUG
+	if (memory_validate(g->heap_id)) {
+	    HEAP_ERROR(g->heap_id);
+	}
+#endif
 	g->flags |= CGF_PACKED | CGF_ROWS;
 
 	if (!onlyexpl && pp) {
@@ -1624,6 +1665,11 @@ calclist(int showall)
 	for (g = amatches; g; g = g->next) {
 	    glines = 0;
 
+#ifdef ZSH_HEAP_DEBUG
+	    if (memory_validate(g->heap_id)) {
+		HEAP_ERROR(g->heap_id);
+	    }
+#endif
 	    zfree(g->widths, 0);
 	    g->widths = NULL;
 
@@ -1858,6 +1904,11 @@ calclist(int showall)
     else
 	for (g = amatches; g; g = g->next)
 	{
+#ifdef ZSH_HEAP_DEBUG
+	    if (memory_validate(g->heap_id)) {
+		HEAP_ERROR(g->heap_id);
+	    }
+#endif
 	    zfree(g->widths, 0);
 	    g->widths = NULL;
 	}
@@ -1945,6 +1996,11 @@ printlist(int over, CLPrintFunc printm, int showall)
     for (g = amatches; g; g = g->next) {
 	char **pp = g->ylist;
 
+#ifdef ZSH_HEAP_DEBUG
+	if (memory_validate(g->heap_id)) {
+	    HEAP_ERROR(g->heap_id);
+	}
+#endif
 	if ((e = g->expls)) {
 	    int l;
 
@@ -2144,7 +2200,13 @@ bld_all_str(Cmatch all)
 
     buf[0] = '\0';
 
-    for (g = amatches; g && !g->mcount; g = g->next);
+    for (g = amatches; g && !g->mcount; g = g->next) {
+#ifdef ZSH_HEAP_DEBUG
+	if (memory_validate(g->heap_id)) {
+	    HEAP_ERROR(g->heap_id);
+	}
+#endif
+    }
 
     mp = g->matches;
     while (1) {
@@ -2262,6 +2324,11 @@ list_matches(UNUSED(Hookdef dummy), UNUSED(void *dummy2))
 #endif
 
     dat.matches = amatches;
+#ifdef ZSH_HEAP_DEBUG
+    if (memory_validate(dat.matches->heap_id)) {
+	HEAP_ERROR(dat.matches->heap_id);
+    }
+#endif
     dat.num = nmatches;
     dat.cur = NULL;
     ret = runhookdef(COMPLISTMATCHESHOOK, (void *) &dat);
diff --git a/Src/mem.c b/Src/mem.c
index 9b80f188c..83a4bbad7 100644
--- a/Src/mem.c
+++ b/Src/mem.c
@@ -123,6 +123,57 @@ static Heap heaps;
 
 static Heap fheap;
 
+#ifdef ZSH_HEAP_DEBUG
+/*
+ * The heap ID we'll allocate next.
+ *
+ * We'll avoid using 0 as that means zero-initialised memory
+ * containing a heap ID is (correctly) marked as invalid.
+ */
+static Heapid next_heap_id = (Heapid)1;
+
+/*
+ * The ID of the heap from which we last allocated heap memory.
+ * In theory, since we carefully avoid allocating heap memory during
+ * interrupts, after any call to zhalloc() or wrappers this should
+ * be the ID of the heap containing the memory just returned.
+ */
+/**/
+mod_export Heapid last_heap_id;
+
+/*
+ * Stack of heaps saved by new_heaps().
+ * Assumes old_heaps() will come along and restore it later
+ * (outputs an error if old_heaps() is called out of sequence).
+ */
+LinkList heaps_saved;
+
+/*
+ * Debugging verbosity.  This must be set from a debugger.
+ * An 'or' of bits from the enum heap_debug_verbosity.
+ */
+volatile int heap_debug_verbosity;
+
+/*
+ * Generate a heap identifier that's unique up to unsigned integer wrap.
+ *
+ * For the purposes of debugging we won't bother trying to make a
+ * heap_id globally unique, which would require checking all existing
+ * heaps every time we create an ID and still wouldn't do what we
+ * ideally want, which is to make sure the IDs of valid heaps are
+ * different from the IDs of no-longer-valid heaps.  Given that,
+ * we'll just assume that if we haven't tracked the problem when the
+ * ID wraps we're out of luck.  We could change the type to a long long
+ * if we wanted more room
+ */
+
+static Heapid
+new_heap_id(void)
+{
+    return next_heap_id++;
+}
+#endif
+
 /* Use new heaps from now on. This returns the old heap-list. */
 
 /**/
@@ -137,6 +188,15 @@ new_heaps(void)
     fheap = heaps = NULL;
     unqueue_signals();
 
+#ifdef ZSH_HEAP_DEBUG
+    if (heap_debug_verbosity & HDV_NEW) {
+	fprintf(stderr, "HEAP DEBUG: heap " HEAPID_FMT
+		" saved, new heaps created.\n", h->heap_id);
+    }
+    if (!heaps_saved)
+	heaps_saved = znewlinklist();
+    zpushnode(heaps_saved, h);
+#endif
     return h;
 }
 
@@ -152,6 +212,12 @@ old_heaps(Heap old)
     for (h = heaps; h; h = n) {
 	n = h->next;
 	DPUTS(h->sp, "BUG: old_heaps() with pushed heaps");
+#ifdef ZSH_HEAP_DEBUG
+	if (heap_debug_verbosity & HDV_FREE) {
+	    fprintf(stderr, "HEAP DEBUG: heap " HEAPID_FMT
+		    "freed in old_heaps().\n", h->heap_id);
+	}
+#endif
 #ifdef USE_MMAP
 	munmap((void *) h, h->size);
 #else
@@ -159,6 +225,21 @@ old_heaps(Heap old)
 #endif
     }
     heaps = old;
+#ifdef ZSH_HEAP_DEBUG
+    if (heap_debug_verbosity & HDV_OLD) {
+	fprintf(stderr, "HEAP DEBUG: heap " HEAPID_FMT
+		"restored.\n", heaps->heap_id);
+    }
+    {
+	Heap myold = heaps_saved ? getlinknode(heaps_saved) : NULL;
+	if (old != myold)
+	{
+	    fprintf(stderr, "HEAP DEBUG: invalid old heap " HEAPID_FMT
+		    ", expecting " HEAPID_FMT ".\n", old->heap_id,
+		    myold->heap_id);
+	}
+    }
+#endif
     fheap = NULL;
     unqueue_signals();
 }
@@ -174,6 +255,12 @@ switch_heaps(Heap new)
     queue_signals();
     h = heaps;
 
+#ifdef ZSH_HEAP_DEBUG
+    if (heap_debug_verbosity & HDV_SWITCH) {
+	fprintf(stderr, "HEAP DEBUG: heap temporarily switched from "
+		HEAPID_FMT " to " HEAPID_FMT ".\n", h->heap_id, new->heap_id);
+    }
+#endif
     heaps = new;
     fheap = NULL;
     unqueue_signals();
@@ -202,6 +289,15 @@ pushheap(void)
 	hs->next = h->sp;
 	h->sp = hs;
 	hs->used = h->used;
+#ifdef ZSH_HEAP_DEBUG
+	hs->heap_id = h->heap_id;
+	h->heap_id = new_heap_id();
+	if (heap_debug_verbosity & HDV_PUSH) {
+	    fprintf(stderr, "HEAP DEBUG: heap " HEAPID_FMT " pushed, new id is "
+		    HEAPID_FMT ".\n",
+		    hs->heap_id, h->heap_id);
+	}
+#endif
     }
     unqueue_signals();
 }
@@ -251,6 +347,22 @@ freeheap(void)
 	    if (!fheap && h->used < ARENA_SIZEOF(h))
 		fheap = h;
 	    hl = h;
+#ifdef ZSH_HEAP_DEBUG
+	    /*
+	     * As the free makes the heap invalid, give it a new
+	     * identifier.  We're not popping it, so don't use
+	     * the one in the heap stack.
+	     */
+	    {
+		Heapid new_id = new_heap_id();
+		if (heap_debug_verbosity & HDV_FREE) {
+		    fprintf(stderr, "HEAP DEBUG: heap " HEAPID_FMT
+			    " freed, new id is " HEAPID_FMT ".\n",
+			    h->heap_id, new_id);
+		}
+		h->heap_id = new_id;
+	    }
+#endif
 	} else {
 #ifdef USE_MMAP
 	    munmap((void *) h, h->size);
@@ -291,6 +403,14 @@ popheap(void)
 	    memset(arena(h) + hs->used, 0xff, h->used - hs->used);
 #endif
 	    h->used = hs->used;
+#ifdef ZSH_HEAP_DEBUG
+	    if (heap_debug_verbosity & HDV_POP) {
+		fprintf(stderr, "HEAP DEBUG: heap " HEAPID_FMT
+			" popped, old heap was " HEAPID_FMT ".\n",
+			h->heap_id, hs->heap_id);
+	    }
+	    h->heap_id = hs->heap_id;
+#endif
 	    if (!fheap && h->used < ARENA_SIZEOF(h))
 		fheap = h;
 	    zfree(hs, sizeof(*hs));
@@ -393,6 +513,13 @@ zhalloc(size_t size)
 	    h->used = n;
 	    ret = arena(h) + n - size;
 	    unqueue_signals();
+#ifdef ZSH_HEAP_DEBUG
+	    last_heap_id = h->heap_id;
+	    if (heap_debug_verbosity & HDV_ALLOC) {
+		fprintf(stderr, "HEAP DEBUG: allocated memory from heap "
+			HEAPID_FMT ".\n", h->heap_id);
+	    }
+#endif
 	    return ret;
 	}
     }
@@ -424,6 +551,13 @@ zhalloc(size_t size)
 	h->used = size;
 	h->next = NULL;
 	h->sp = NULL;
+#ifdef ZSH_HEAP_DEBUG
+	h->heap_id = new_heap_id();
+	if (heap_debug_verbosity & HDV_CREATE) {
+	    fprintf(stderr, "HEAP DEBUG: create new heap " HEAPID_FMT ".\n",
+		    h->heap_id);
+	}
+#endif
 
 	if (hp)
 	    hp->next = h;
@@ -432,6 +566,13 @@ zhalloc(size_t size)
 	fheap = h;
 
 	unqueue_signals();
+#ifdef ZSH_HEAP_DEBUG
+	last_heap_id = h->heap_id;
+	if (heap_debug_verbosity & HDV_ALLOC) {
+	    fprintf(stderr, "HEAP DEBUG: allocated memory from heap "
+		    HEAPID_FMT ".\n", h->heap_id);
+	}
+#endif
 	return arena(h);
     }
 }
@@ -495,6 +636,9 @@ hrealloc(char *p, size_t old, size_t new)
      * don't use the heap for anything else.)
      */
     if (p == arena(h)) {
+#ifdef ZSH_HEAP_DEBUG
+	Heapid heap_id = h->heap_id;
+#endif
 	/*
 	 * Zero new seems to be a special case saying we've finished
 	 * with the specially reallocated memory, see scanner() in glob.c.
@@ -554,6 +698,9 @@ hrealloc(char *p, size_t old, size_t new)
 		heaps = h;
 	}
 	h->used = new;
+#ifdef ZSH_HEAP_DEBUG
+	h->heap_id = heap_id;
+#endif
 	unqueue_signals();
 	return arena(h);
     }
@@ -576,6 +723,53 @@ hrealloc(char *p, size_t old, size_t new)
     }
 }
 
+#ifdef ZSH_HEAP_DEBUG
+/*
+ * Check if heap_id is the identifier of a currently valid heap,
+ * including any heap buried on the stack, or of permanent memory.
+ * Return 0 if so, else 1.
+ *
+ * This gets confused by use of switch_heaps().  That's because so do I.
+ */
+
+/**/
+mod_export int
+memory_validate(Heapid heap_id)
+{
+    Heap h;
+    Heapstack hs;
+    LinkNode node;
+
+    if (heap_id == HEAPID_PERMANENT)
+	return 0;
+
+    queue_signals();
+    for (h = heaps; h; h = h->next) {
+	if (h->heap_id == heap_id)
+	    return 0;
+	for (hs = heaps->sp; hs; hs = hs->next) {
+	    if (hs->heap_id == heap_id)
+		return 0;
+	}
+    }
+
+    if (heaps_saved) {
+	for (node = firstnode(heaps_saved); node; incnode(node)) {
+	    for (h = (Heap)getdata(node); h; h = h->next) {
+		if (h->heap_id == heap_id)
+		    return 0;
+		for (hs = heaps->sp; hs; hs = hs->next) {
+		    if (hs->heap_id == heap_id)
+			return 0;
+		}
+	    }
+	}
+    }
+
+    return 1;
+}
+#endif
+
 /* allocate memory from the current memory pool and clear it */
 
 /**/
diff --git a/Src/zsh.h b/Src/zsh.h
index 51ec937fe..f389a900e 100644
--- a/Src/zsh.h
+++ b/Src/zsh.h
@@ -2327,11 +2327,67 @@ enum {
  * Memory management *
  *********************/
 
+#ifdef ZSH_HEAP_DEBUG
+/*
+ * A Heapid is a type for identifying, uniquely up to the point where
+ * the count of new identifiers wraps. all heaps that are or
+ * (importantly) have been valid.  Each valid heap is given an
+ * identifier, and every time we push a heap we save the old identifier
+ * and give the heap a new identifier so that when the heap is popped
+ * or freed we can spot anything using invalid memory from the popped
+ * heap.
+ *
+ * We could make this unsigned long long if we wanted a big range.
+ */
+typedef unsigned int Heapid;
+
+/* printf format specifier corresponding to Heapid */
+#define HEAPID_FMT	"%x"
+
+/* Marker that memory is permanently allocated */
+#define HEAPID_PERMANENT (UINT_MAX)
+
+/*
+ * Heap debug verbosity.
+ * Bits to be 'or'ed into the variable also called heap_debug_verbosity.
+ */
+enum heap_debug_verbosity {
+    /* Report when we push a heap */
+    HDV_PUSH = 0x01,
+    /* Report when we pop a heap */
+    HDV_POP = 0x02,
+    /* Report when we create a new heap from which to allocate */
+    HDV_CREATE = 0x04,
+    /* Report every time we free a complete heap */
+    HDV_FREE = 0x08,
+    /* Report when we temporarily install a new set of heaps */
+    HDV_NEW = 0x10,
+    /* Report when we restore an old set of heaps */
+    HDV_OLD = 0x20,
+    /* Report when we temporarily switch heaps */
+    HDV_SWITCH = 0x40,
+    /*
+     * Report every time we allocate memory from the heap.
+     * This is very verbose, and arguably not very useful: we
+     * would expect to allocate memory from a heap we create.
+     * For much debugging heap_debug_verbosity = 0x7f should be sufficient.
+     */
+    HDV_ALLOC = 0x80
+};
+
+#define HEAP_ERROR(heap_id)			\
+    fprintf(stderr, "%s:%d: HEAP DEBUG: invalid heap: " HEAPID_FMT ".\n", \
+	    __FILE__, __LINE__, heap_id)
+#endif
+
 /* heappush saves the current heap state using this structure */
 
 struct heapstack {
     struct heapstack *next;	/* next one in list for this heap */
     size_t used;
+#ifdef ZSH_HEAP_DEBUG
+    Heapid heap_id;
+#endif
 };
 
 /* A zsh heap. */
@@ -2342,6 +2398,10 @@ struct heap {
     size_t used;		/* bytes used from the heap                  */
     struct heapstack *sp;	/* used by pushheap() to save the value used */
 
+#ifdef ZSH_HEAP_DEBUG
+    unsigned int heap_id;
+#endif
+
 /* Uncomment the following if the struct needs padding to 64-bit size. */
 /* Make sure sizeof(heap) is a multiple of 8 
 #if defined(PAD_64_BIT) && !defined(__GNUC__)