From 506615ff94c7784e974f13034f04db87427b367e Mon Sep 17 00:00:00 2001 From: Peter Stephenson Date: Sat, 14 May 2011 00:07:41 +0000 Subject: 29267: add -enable-zsh-debug and use for debugging completion matcher groups --- ChangeLog | 9 ++- Src/Zle/comp.h | 3 + Src/Zle/compcore.c | 31 ++++++++ Src/Zle/compctl.c | 5 ++ Src/Zle/complist.c | 20 ++++++ Src/Zle/compresult.c | 79 +++++++++++++++++++-- Src/mem.c | 194 +++++++++++++++++++++++++++++++++++++++++++++++++++ Src/zsh.h | 60 ++++++++++++++++ configure.ac | 12 ++++ 9 files changed, 406 insertions(+), 7 deletions(-) diff --git a/ChangeLog b/ChangeLog index c5431af71..8b96891b7 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,10 @@ +2011-05-14 Peter Stephenson + + * 29267: configure.ac, Src/mem.c, Src/zsh.h, Src/Zle/comp.h, + Src/Zle/compcore.c, Src/Zle/compctl.c, Src/Zle/complist.c, + Src/Zle/compresult.c: add --enable-zsh-hash-debug and use + for debugging completion matcher groups. + 2011-05-13 Peter Stephenson * Danek: 29254: Src/cond.c, Src/Builtins/rlimits.c, @@ -14688,5 +14695,5 @@ ***************************************************** * This is used by the shell to define $ZSH_PATCHLEVEL -* $Revision: 1.5298 $ +* $Revision: 1.5299 $ ***************************************************** 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__) diff --git a/configure.ac b/configure.ac index 116de2e4f..1ce815ca5 100644 --- a/configure.ac +++ b/configure.ac @@ -105,6 +105,18 @@ AC_HELP_STRING([--enable-zsh-secure-free], [turn on error checking for free()]), AC_DEFINE(ZSH_SECURE_FREE) fi]) +dnl Do you want to debug zsh heap allocation? +dnl Does not depend on zsh-mem. +ifdef([zsh-heap-debug],[undefine([zsh-heap-debug])])dnl +AH_TEMPLATE([ZSH_HEAP_DEBUG], +[Define to 1 if you want to turn on error checking for heap allocation.]) +AC_ARG_ENABLE(zsh-heap-debug, +AC_HELP_STRING([--enable-zsh-heap-debug], +[turn on error checking for heap allocation]), +[if test x$enableval = xyes; then + AC_DEFINE(ZSH_HEAP_DEBUG) +fi]) + dnl Do you want debugging information on internal hash tables. dnl This turns on the `hashinfo' builtin command. ifdef([zsh-hash-debug],[undefine([zsh-hash-debug])])dnl -- cgit 1.4.1