|
|
Subject: Re: Bug#385720: m4: INTERNAL ERROR: recursive push_string (fwd) - msg#00001
List: gnu.m4.patches
-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA1
According to Paul Eggert on 9/4/2006 2:16 AM:
> > In the meantime, perhaps Autoconf should document that
> > all autom4te input files should always end in newline.
>
> Naaaah. Let's just fix the bug in M4. It's clearly a bug.
> The GNU tradition is to handle arbitrary input files,
> and not to insist on "text" files in the POSIX sense.
>
OK, I found the culprit; the regression crept in on Aug 1 (post 1.4.5).
My fix to debian bug 175365 (remembering the current file and line, rather
than printing NONE:0: when diagnosing incomplete input) was not expecting
the last token in a file to be a macro expansion with no arguments. In
addition to the workaround of adding the newline, you can also fix things
by calling AC_OUTPUT with arguments (fortunately, AC_OUTPUT will ignore
those arguments, so you aren't changing the syntax of your configure.ac),
or introducing any other non-macro token (such as an empty string [],
extra whitespace, etc.). Here's the patch I'm applying, if debian wants
to release 1.4.6-2 instead of waiting for 1.4.7. And wow is it hard
adding files to the testsuite that don't end in newline, so instead I used
changequote to allow newline in a macro name to trigger a similar failure
in the 'make distcheck' testsuite, to ensure we don't regress again.
2006-09-04 Eric Blake <ebb9@xxxxxxx>
* src/input.c (peek_input): Fix regression in handling macro
without arguments as last token in file; debian bug 385720.
(next_token): Always consume an input character.
Reported by Andreas Schultz.
* configure.ac (AC_INIT): Bump version number.
* NEWS: Document this fix.
* doc/m4.texinfo (History): Mention next version.
(Changeword): Add example that exposes this bug.
* THANKS: Update.
- --
Life is short - so eat dessert first!
Eric Blake ebb9@xxxxxxx
-----BEGIN PGP SIGNATURE-----
Version: GnuPG v1.4.2.1 (Cygwin)
Comment: Public key at home.comcast.net/~ericblake/eblake.gpg
Comment: Using GnuPG with Mozilla - http://enigmail.mozdev.org
iD8DBQFE/CkR84KuGfSFAYARAmr0AKCy6FIJDZzNWnN8nEvy7EdpJHpurQCgkHX4
VFXV6/1+nUcdhnNDrC7q93E=
=WNhB
-----END PGP SIGNATURE-----
Index: NEWS
===================================================================
RCS file: /sources/m4/m4/NEWS,v
retrieving revision 1.1.1.1.2.57
diff -u -p -r1.1.1.1.2.57 NEWS
--- NEWS 25 Aug 2006 14:08:33 -0000 1.1.1.1.2.57
+++ NEWS 4 Sep 2006 13:15:47 -0000
@@ -2,6 +2,11 @@ GNU M4 NEWS - User visible changes.
Copyright (C) 1992, 1993, 1994, 2004, 2005, 2006 Free Software
Foundation, Inc.
+Version 1.4.7 - ?? ??? 2006, by ?? (CVS version 1.4.6a)
+
+* Fix regression from 1.4.5 in handling a file that ends in a macro
+ expansion without arguments instead of a newline.
+
Version 1.4.6 - 25 August 2006, by Eric Blake (CVS version 1.4.5a)
* Fix buffer overruns in regexp and patsubst macros when handed a trailing
Index: configure.ac
===================================================================
RCS file: /sources/m4/m4/configure.ac,v
retrieving revision 1.36.2.25
diff -u -p -r1.36.2.25 configure.ac
--- configure.ac 25 Aug 2006 14:08:33 -0000 1.36.2.25
+++ configure.ac 4 Sep 2006 13:15:47 -0000
@@ -18,7 +18,7 @@
# 02110-1301 USA
AC_PREREQ([2.60])
-AC_INIT([GNU M4], [1.4.6], [bug-m4@xxxxxxx])
+AC_INIT([GNU M4], [1.4.6a], [bug-m4@xxxxxxx])
AM_INIT_AUTOMAKE([1.9.6 dist-bzip2 gnu])
PACKAGE=$PACKAGE_TARNAME; AC_SUBST([PACKAGE])
VERSION=$PACKAGE_VERSION; AC_SUBST([VERSION])
Index: doc/m4.texinfo
===================================================================
RCS file: /sources/m4/m4/doc/m4.texinfo,v
retrieving revision 1.1.1.1.2.70
diff -u -p -r1.1.1.1.2.70 m4.texinfo
--- doc/m4.texinfo 24 Aug 2006 14:27:33 -0000 1.1.1.1.2.70
+++ doc/m4.texinfo 4 Sep 2006 13:15:48 -0000
@@ -360,7 +360,7 @@ addressed some long standing bugs in the
Then in 2005 Gary V. Vaughan collected together the many
patches to @acronym{GNU} @code{m4} 1.4 that were floating around the net and
released 1.4.3 and 1.4.4. And in 2006, Eric Blake joined the team and
-prepared patches for the release of 1.4.5 and 1.4.6.
+prepared patches for the release of 1.4.5, 1.4.6, and 1.4.7.
Meanwhile, development has continued on new features for @code{m4}, such
as dynamic module loading and additional builtins. When complete,
@@ -2724,6 +2724,41 @@ is a restriction on the regular expressi
@code{changeword}. This is that if your regular expression accepts
@samp{foo}, it must also accept @samp{f} and @samp{fo}.
+@example
+ifdef(`changeword', `', `errprint(` skipping: no changeword support
+')m4exit(`77')')dnl
+define(`foo
+', `bar
+')
+@result{}
+dnl This example wants to recognize changeword, dnl, and `foo\n'.
+dnl First, we check that our regexp will match.
+regexp(`changeword', `[cd][a-z]*\|foo[
+]')
+@result{}0
+regexp(`foo
+', `[cd][a-z]*\|foo[
+]')
+@result{}0
+regexp(`f', `[cd][a-z]*\|foo[
+]')
+@result{}-1
+foo
+@result{}foo
+changeword(`[cd][a-z]*\|foo[
+]')
+@result{}
+dnl Even though `foo\n' matches, we forgot to allow `f'.
+foo
+@result{}foo
+changeword(`[cd][a-z]*\|fo*[
+]?')
+@result{}
+dnl Now we can call `foo\n'.
+foo
+@result{}bar
+@end example
+
@code{changeword} has another function. If the regular expression
supplied contains any grouped subexpressions, then text outside
the first of these is discarded before symbol lookup. So:
Index: src/input.c
===================================================================
RCS file: /sources/m4/m4/src/Attic/input.c,v
retrieving revision 1.1.1.1.2.21
diff -u -p -r1.1.1.1.2.21 input.c
--- src/input.c 23 Aug 2006 11:30:11 -0000 1.1.1.1.2.21
+++ src/input.c 4 Sep 2006 13:15:48 -0000
@@ -455,8 +455,12 @@ peek_input (void)
"INTERNAL ERROR: input stack botch in peek_input ()"));
abort ();
}
- /* End of input source --- pop one level. */
- pop_input ();
+ /* End of current input source --- pop one level if another
+ level still exists. */
+ if (isp->prev != NULL)
+ pop_input ();
+ else
+ return CHAR_EOF;
}
}
@@ -783,18 +787,20 @@ next_token (token_data *td)
obstack_1grow (&token_stack, '\0');
token_bottom = obstack_finish (&token_stack);
+ /* Can't consume character until after CHAR_MACRO is handled. */
ch = peek_input ();
if (ch == CHAR_EOF)
{
#ifdef DEBUG_INPUT
fprintf (stderr, "next_token -> EOF\n");
#endif
+ next_char ();
return TOKEN_EOF;
}
if (ch == CHAR_MACRO)
{
init_macro_token (td);
- (void) next_char ();
+ next_char ();
#ifdef DEBUG_INPUT
fprintf (stderr, "next_token -> MACDEF (%s)\n",
find_builtin_by_addr (TOKEN_DATA_FUNC (td))->name);
@@ -802,7 +808,7 @@ next_token (token_data *td)
return TOKEN_MACDEF;
}
- (void) next_char ();
+ next_char (); /* Consume character we already peeked at. */
if (MATCH (ch, bcomm.string, TRUE))
{
obstack_grow (&token_stack, bcomm.string, bcomm.length);
_______________________________________________
M4-patches mailing list
M4-patches@xxxxxxx
http://lists.gnu.org/mailman/listinfo/m4-patches
Was this page helpful?
Thread at a glance:
Previous Message by Date:
click to view message preview
plug some memory bugs
It's a wonder that CVS head even ran; it has had a latent bug since Oct 2003
that caused out-of-bounds writes to the heap on bootup, even before option
processing starts! I also narrowed down why this crashes:
$ echo 'define(bar,1)define(baz,2)renamesyms(^ba.,baa)'|m4
Our hash table is not prepared to handle multiple entries with equivalent keys
(and even documented this, but not very well - node_insert mentioned that
inserting equal keys should just shadow the older entry, but bucket_insert
mentioned that relative ordering is not preserved). When we go to traverse the
table for cleanup, we do a lookup for the shadowed "baa" key being visited by
the iterator, but find the other "baa" key in the table, and then try to delete
the element not pointed to by the iterator, which breaks the iteration. I
added some assertion framework that catches this and other hash-table no-nos.
This also fixes some potential memory leaks, if any hash table iterator
callback ever returns non-NULL. Now it is a question of how we should fix
renamesyms to quit mapping multiple macros to the same name, and ending up with
multiple entries in the hashtable; or add an API so that it is easier to delete
the element being visited by the iterator without having to repeat a key lookup
(and perhaps find the wrong entry); or something else altogether.
Meanwhile, we were compiling getopt twice. And finally, I consistently used
the keyword "module" for all tests in the testsuite that depend on dynamic
modules (and which fail when ./configure --disable-shared).
2006-09-01 Eric Blake <ebb9@xxxxxxx>
* m4/m4.c (m4_create): Fix latent bug since 2003-10-08.
* m4/hash.h (m4_free_hash_iterator): New prototype.
* m4/hash.c (struct m4_hash) [!NDEBUG]: Add iter member, to
ensure we don't do unsafe things while iterating.
(HASH_ITER, ITER_CHAIN): New accessor macros.
(m4_hash_new, m4_hash_resize, maybe_grow): Fix malloc typing bug.
(m4_hash_delete, m4_hash_insert): Unsafe while iterating.
(m4_hash_remove) [!NDEBUG]: Enforce safety while iterating.
(m4_get_hash_iterator_next) [!NDEBUG]: Track current iterators,
to catch unsafe actions.
(m4_free_hash_iterator): New function, to avoid memory leaks, and
when debugging, to track safe actions.
(m4_hash_apply): Avoid memory leak.
* m4/symtab.c (m4_symtab_apply): Likewise.
* ltdl/m4/gnulib-cache.m4: Remove getopt from here; it is pulled
in manually to src/ for now.
* tests/builtins.at (gmp): Add keyword module.
* tests/modules.at (Freezing modules, modules: shadow)
(modules: unload, modules: importing, modules: trace): Likewise.
Index: ltdl/m4/gnulib-cache.m4
===================================================================
RCS file: /sources/m4/m4/ltdl/m4/gnulib-cache.m4,v
retrieving revision 1.9
diff -u -r1.9 gnulib-cache.m4
--- ltdl/m4/gnulib-cache.m4 25 Aug 2006 19:23:02 -0000 1.9
+++ ltdl/m4/gnulib-cache.m4 1 Sep 2006 23:01:45 -0000
@@ -15,15 +15,17 @@
# Specification in the form of a command-line invocation:
-# gnulib-tool --import --dir=. --lib=libgnu --source-base=gnu --m4-
base=ltdl/m4 --doc-base=doc --aux-dir=ltdl/config --libtool --macro-prefix=M4
assert binary-io cloexec close-stream dirname error exit fdl filenamecat fopen-
safer free gendocs getopt gettext gnupload mkstemp obstack progname regex
regexprops-generic stdbool stdlib-safer strtol unlocked-io verror xalloc xalloc-
die xstrndup xvasprintf
+# gnulib-tool --import --dir=. --lib=libgnu --source-base=gnu --m4-
base=ltdl/m4 --doc-base=doc --aux-dir=ltdl/config --libtool --macro-prefix=M4
assert binary-io cloexec close-stream dirname error exit fdl filenamecat fopen-
safer free gendocs gettext gnupload mkstemp obstack progname regex regexprops-
generic stdbool stdlib-safer strtol unlocked-io verror xalloc xalloc-die
xstrndup xvasprintf
# Specification in the form of a few gnulib-tool.m4 macro invocations:
-gl_MODULES([assert binary-io cloexec close-stream dirname error exit fdl
filenamecat fopen-safer free gendocs getopt gettext gnupload mkstemp obstack
progname regex regexprops-generic stdbool stdlib-safer strtol unlocked-io
verror xalloc xalloc-die xstrndup xvasprintf])
+gl_LOCAL_DIR([])
+gl_MODULES([assert binary-io cloexec close-stream dirname error exit fdl
filenamecat fopen-safer free gendocs gettext gnupload mkstemp obstack progname
regex regexprops-generic stdbool stdlib-safer strtol unlocked-io verror xalloc
xalloc-die xstrndup xvasprintf])
gl_AVOID([])
gl_SOURCE_BASE([gnu])
gl_M4_BASE([ltdl/m4])
gl_DOC_BASE([doc])
gl_TESTS_BASE([tests])
gl_LIB([libgnu])
+gl_MAKEFILE_NAME([])
gl_LIBTOOL
gl_MACRO_PREFIX([M4])
Index: m4/hash.c
===================================================================
RCS file: /sources/m4/m4/m4/hash.c,v
retrieving revision 1.19
diff -u -r1.19 hash.c
--- m4/hash.c 28 Jul 2006 01:49:37 -0000 1.19
+++ m4/hash.c 1 Sep 2006 23:01:45 -0000
@@ -37,6 +37,9 @@
m4_hash_hash_func *hash_func;
m4_hash_cmp_func *cmp_func;
hash_node **buckets;
+#ifndef NDEBUG
+ m4_hash_iterator *iter; /* current iterator */
+#endif
};
struct hash_node
@@ -47,6 +50,17 @@
};
+struct m4_hash_iterator
+{
+ const m4_hash *hash; /* contains the buckets */
+ hash_node * place; /* the node we are about to return */
+ hash_node * next; /* the next node, incase PLACE is removed */
+ size_t next_bucket; /* the next bucket index following NEXT */
+#ifndef NDEBUG
+ m4_hash_iterator *chain; /* multiple iterators visiting one hash */
+#endif
+};
+
#define HASH_SIZE(hash) ((hash)->size)
#define HASH_LENGTH(hash) ((hash)->length)
@@ -58,13 +72,28 @@
#define NODE_KEY(node) ((node)->key)
#define NODE_VALUE(node) ((node)->value)
+#define ITERATOR_HASH(i) ((i)->hash)
+#define ITERATOR_PLACE(i) ((i)->place)
+#define ITERATOR_NEXT(i) ((i)->next)
+#define ITERATOR_NEXT_BUCKET(i) ((i)->next_bucket)
+
+#define ITERATOR_NEXT_NEXT(i) NODE_NEXT (ITERATOR_PLACE (i))
+
/* Helper macros. */
#define BUCKET_NTH(hash, n) (HASH_BUCKETS (hash)[n])
#define BUCKET_COUNT(hash, key) \
- ((*HASH_HASH_FUNC(hash))(key) % HASH_SIZE(hash))
+ ((*HASH_HASH_FUNC (hash))(key) % HASH_SIZE (hash))
#define BUCKET_KEY(hash, key) \
(BUCKET_NTH ((hash), BUCKET_COUNT ((hash), (key))))
+/* Debugging macros. */
+#ifdef NDEBUG
+# define HASH_ITER(hash) 0
+# define ITER_CHAIN(iter) 0
+#else
+# define HASH_ITER(hash) (((m4_hash *) hash)->iter)
+# define ITER_CHAIN(iter) ((iter)->chain)
+#endif
static void bucket_insert (m4_hash *hash, hash_node *bucket);
@@ -77,7 +106,7 @@
-static hash_node *free_list = 0;
+static hash_node *free_list = NULL;
@@ -99,9 +128,12 @@
hash = xmalloc (sizeof *hash);
HASH_SIZE (hash) = size;
HASH_LENGTH (hash) = 0;
- HASH_BUCKETS (hash) = xcalloc (size, sizeof HASH_BUCKETS (hash));
+ HASH_BUCKETS (hash) = xcalloc (size, sizeof *HASH_BUCKETS (hash));
HASH_HASH_FUNC (hash) = hash_func;
HASH_CMP_FUNC (hash) = cmp_func;
+#ifndef NDEBUG
+ HASH_ITER (hash) = NULL;
+#endif
return hash;
}
@@ -123,15 +155,17 @@
}
/* Recycle each of the nodes in HASH onto the free list, and release
- the rest of the memory used by the table. Memory addressed by
- the recycled nodes is _NOT_ freed: this needs to be done manually
- to prevent memory leaks. */
+ the rest of the memory used by the table. Memory addressed by the
+ recycled nodes is _NOT_ freed: this needs to be done manually to
+ prevent memory leaks. This is not safe to call while HASH is being
+ iterated. */
void
m4_hash_delete (m4_hash *hash)
{
size_t i;
assert (hash);
+ assert (!HASH_ITER (hash));
for (i = 0; i < HASH_SIZE (hash); ++i)
if (BUCKET_NTH (hash, i))
@@ -154,16 +188,16 @@
for (node = BUCKET_NTH (hash, i); node->next; node = NODE_NEXT (node))
{
- assert (NODE_KEY(node) == 0);
+ assert (NODE_KEY (node) == NULL);
--HASH_LENGTH (hash);
}
- assert (NODE_KEY(node) == 0);
+ assert (NODE_KEY (node) == NULL);
--HASH_LENGTH (hash);
NODE_NEXT (node) = free_list;
free_list = BUCKET_NTH (hash, i);
- BUCKET_NTH (hash, i) = 0;
+ BUCKET_NTH (hash, i) = NULL;
}
/* Create and initialise a new node with KEY and VALUE, by reusing a
@@ -171,7 +205,7 @@
static hash_node *
node_new (const void *key, void *value)
{
- hash_node *node = 0;
+ hash_node *node = NULL;
if (free_list)
{
@@ -185,7 +219,7 @@
assert (node);
- NODE_NEXT (node) = 0;
+ NODE_NEXT (node) = NULL;
NODE_KEY (node) = key;
NODE_VALUE (node) = value;
@@ -197,7 +231,7 @@
node_delete (m4_hash *hash, hash_node *node)
{
assert (node);
- assert (NODE_KEY(node) == 0);
+ assert (NODE_KEY (node) == NULL);
NODE_NEXT (node) = free_list;
free_list = node;
@@ -205,15 +239,18 @@
--HASH_LENGTH (hash);
}
-/* Create a new entry in HASH with KEY and VALUE, making use of
- nodes in the free list if possible, and potentially growing
- the size of the table if node density is too high. */
+/* Create a new entry in HASH with KEY and VALUE, making use of nodes
+ in the free list if possible, and potentially growing the size of
+ the table if node density is too high. This is not safe to call
+ while HASH is being iterated. Currently, it is not safe to call
+ this if another entry already matches KEY. */
const void *
m4_hash_insert (m4_hash *hash, const void *key, void *value)
{
hash_node *node;
assert (hash);
+ assert (!HASH_ITER (hash));
node = node_new (key, value);
node_insert (hash, node);
@@ -233,7 +270,7 @@
assert (hash);
assert (node);
- assert (NODE_NEXT (node) == 0);
+ assert (NODE_NEXT (node) == NULL);
n = BUCKET_COUNT (hash, NODE_KEY (node));
NODE_NEXT (node) = BUCKET_NTH (hash, n);
@@ -242,21 +279,32 @@
++HASH_LENGTH (hash);
}
-/* Remove from HASH, the first node with key KEY; comparing keys
- with HASH's cmp_func. Any nodes with the same KEY previously
- hidden by the removed node will become visible again. The key
- field of the removed node is returned. */
+/* Remove from HASH, the first node with key KEY; comparing keys with
+ HASH's cmp_func. Any nodes with the same KEY previously hidden by
+ the removed node will become visible again. The key field of the
+ removed node is returned, or the original KEY If there was no
+ match. This is unsafe if multiple iterators are visiting HASH, or
+ when a lone iterator is visiting on a different key. */
void *
m4_hash_remove (m4_hash *hash, const void *key)
{
size_t n;
+#ifndef NDEBUG
+ m4_hash_iterator *iter = HASH_ITER (hash);
+
assert (hash);
+ if (HASH_ITER (hash))
+ {
+ assert (!ITER_CHAIN (iter));
+ assert (ITERATOR_PLACE (iter));
+ }
+#endif
n = BUCKET_COUNT (hash, key);
{
- hash_node *node = 0;
+ hash_node *node = NULL;
do
{
@@ -271,7 +319,9 @@
key = NODE_KEY (next);
#ifndef NDEBUG
- NODE_KEY (next) = 0;
+ if (iter)
+ assert (ITERATOR_PLACE (iter) == next);
+ NODE_KEY (next) = NULL;
#endif
node_delete (hash, next);
break;
@@ -284,11 +334,12 @@
return (void *) key;
}
-/* Return the address of the value field of the first node in
- HASH that has a matching KEY. The address is returned so that
- an explicit 0 value can be distinguished from a failed lookup
- (also 0). Fortuitously for M4, this also means that the value
- field can be changed `in situ' to implement a value stack. */
+/* Return the address of the value field of the first node in HASH
+ that has a matching KEY. The address is returned so that an
+ explicit NULL value can be distinguished from a failed lookup (also
+ NULL). Fortuitously for M4, this also means that the value field
+ can be changed `in situ' to implement a value stack. Safe to call
+ even when an iterator is in force. */
void **
m4_hash_lookup (m4_hash *hash, const void *key)
{
@@ -298,7 +349,7 @@
node = node_lookup (hash, key);
- return node ? &NODE_VALUE (node) : 0;
+ return node ? &NODE_VALUE (node) : NULL;
}
/* Return the first node in HASH that has a matching KEY. */
@@ -317,7 +368,8 @@
return node;
}
-/* How many entries are currently contained by HASH. */
+/* How many entries are currently contained by HASH. Safe to call
+ even during an interation. */
size_t
m4_get_hash_length (m4_hash *hash)
{
@@ -334,7 +386,8 @@
to set a smaller or larger initial size if you know in advance what
order of magnitude of entries will be in the table. Be aware that
the efficiency of the lookup and grow features require that the size
- always be 1 less than a power of 2. */
+ always be 1 less than a power of 2. Unsafe if HASH is being visited
+ by an iterator. */
void
m4_hash_resize (m4_hash *hash, size_t size)
{
@@ -342,12 +395,13 @@
size_t original_size;
assert (hash);
+ assert (!HASH_ITER (hash));
original_size = HASH_SIZE (hash);
original_buckets = HASH_BUCKETS (hash);
HASH_SIZE (hash) = size;
- HASH_BUCKETS (hash) = xcalloc (size, sizeof HASH_BUCKETS (hash));
+ HASH_BUCKETS (hash) = xcalloc (size, sizeof *HASH_BUCKETS (hash));
{
size_t i;
@@ -377,8 +431,9 @@
hash_node **original_buckets = HASH_BUCKETS (hash);
/* HASH sizes are always 1 less than a power of 2. */
- HASH_SIZE (hash) = (2* (1+ original_size)) -1;
- HASH_BUCKETS (hash) = xcalloc (hash->size, sizeof HASH_BUCKETS (hash));
+ HASH_SIZE (hash) = (2 * (1 + original_size)) -1;
+ HASH_BUCKETS (hash) = xcalloc (HASH_SIZE (hash),
+ sizeof *HASH_BUCKETS (hash));
{
size_t i;
@@ -392,7 +447,8 @@
}
/* Insert each node in BUCKET into HASH. Relative ordering of nodes
- is not preserved. */
+ is not preserved. This would need to change if we were to
+ guarantee relative ordering within a bucket for identical keys. */
static void
bucket_insert (m4_hash *hash, hash_node *bucket)
{
@@ -404,7 +460,7 @@
hash_node *next = NODE_NEXT (bucket);
/* Break link to rest of the bucket before reinserting. */
- NODE_NEXT (bucket) = 0;
+ NODE_NEXT (bucket) = NULL;
node_insert (hash, bucket);
bucket = next;
@@ -412,6 +468,9 @@
while (bucket);
}
+/* Reclaim all memory used by free nodes. Safe to call at any time,
+ although only worth calling at program shutdown to verify no
+ leaks. */
void
m4_hash_exit (void)
{
@@ -425,21 +484,14 @@
-struct m4_hash_iterator
-{
- const m4_hash *hash; /* contains the buckets */
- hash_node * place; /* the node we are about to return */
- hash_node * next; /* the next node, incase PLACE is removed */
- size_t next_bucket; /* the next bucket index following NEXT */
-};
-
-#define ITERATOR_HASH(i) ((i)->hash)
-#define ITERATOR_PLACE(i) ((i)->place)
-#define ITERATOR_NEXT(i) ((i)->next)
-#define ITERATOR_NEXT_BUCKET(i) ((i)->next_bucket)
-
-#define ITERATOR_NEXT_NEXT(i) NODE_NEXT (ITERATOR_PLACE (i))
-
+/* Iterate over a given HASH. Start with PLACE being NULL, then
+ repeat with PLACE being the previous return value. The return
+ value is the current location of the iterator, or NULL when the
+ walk is complete. Call m4_free_hash_iterator to abort iteration.
+ During the iteration, it is safe to search the list, and if no
+ other iterator is active, it is safe to remove the key pointed to
+ by this iterator. All other actions that modify HASH are
+ unsafe. */
m4_hash_iterator *
m4_get_hash_iterator_next (const m4_hash *hash, m4_hash_iterator *place)
{
@@ -451,6 +503,10 @@
{
place = xzalloc (sizeof *place);
ITERATOR_HASH (place) = hash;
+#ifndef NDEBUG
+ ITER_CHAIN (place) = HASH_ITER (hash);
+ HASH_ITER (hash) = place;
+#endif
}
next:
@@ -465,7 +521,7 @@
{
/* Find the next non-empty bucket. */
while ((ITERATOR_NEXT_BUCKET (place) < HASH_SIZE (hash))
- && (BUCKET_NTH (hash, ITERATOR_NEXT_BUCKET (place)) == 0))
+ && (BUCKET_NTH (hash, ITERATOR_NEXT_BUCKET (place)) == NULL))
{
++ITERATOR_NEXT_BUCKET (place);
}
@@ -477,7 +533,7 @@
= BUCKET_NTH (hash, ITERATOR_NEXT_BUCKET (place));
}
else
- ITERATOR_NEXT (place) = 0;
+ ITERATOR_NEXT (place) = NULL;
/* Advance the `next' reference. */
++ITERATOR_NEXT_BUCKET (place);
@@ -486,8 +542,8 @@
/* If there are no more nodes to return, recycle the iterator memory. */
if (! (ITERATOR_PLACE (place) || ITERATOR_NEXT (place)))
{
- free (place);
- return 0;
+ m4_free_hash_iterator (hash, place);
+ return NULL;
}
/* On the first call we need to put the 1st node in PLACE and
@@ -500,6 +556,38 @@
return place;
}
+/* Clean up the iterator PLACE within HASH when aborting an iteration
+ early. */
+void
+m4_free_hash_iterator (const m4_hash *hash, m4_hash_iterator *place)
+{
+#ifndef NDEBUG
+ m4_hash_iterator *iter = NULL;
+ m4_hash_iterator *next;
+
+ assert (hash);
+ assert (place && (ITERATOR_HASH (place) == hash));
+
+ do
+ {
+ next = iter ? ITER_CHAIN (iter) : HASH_ITER (hash);
+ if (place == next)
+ {
+ if (iter)
+ ITER_CHAIN (iter) = ITER_CHAIN (next);
+ else
+ HASH_ITER (hash) = ITER_CHAIN (next);
+ break;
+ }
+ iter = next;
+ }
+ while (iter);
+ assert (next);
+#endif
+ free (place);
+}
+
+/* Return the key being visited by the iterator PLACE. */
const void *
m4_get_hash_iterator_key (m4_hash_iterator *place)
{
@@ -508,6 +596,7 @@
return NODE_KEY (ITERATOR_PLACE (place));
}
+/* Return the value being visited by the iterator PLACE. */
void *
m4_get_hash_iterator_value (m4_hash_iterator *place)
{
@@ -517,10 +606,12 @@
}
/* The following function is used for the cases where we want to do
- something to each and every entry in HASH. This function
- traverses the hash table, and calls a specified function FUNC for
- each entry in the table. FUNC is called with a pointer to the
- entry key, value, and the passed DATA argument. */
+ something to each and every entry in HASH. This function traverses
+ the hash table, and calls a specified function FUNC for each entry
+ in the table. FUNC is called with a pointer to the entry key,
+ value, and the passed DATA argument. If FUNC returns non-NULL,
+ abort the iteration and return that value; a return of NULL implies
+ success on all entries. */
void *
m4_hash_apply (m4_hash *hash, m4_hash_apply_func *func, void *userdata)
{
@@ -536,7 +627,10 @@
m4_get_hash_iterator_value (place), userdata);
if (result != NULL)
- break;
+ {
+ m4_free_hash_iterator (hash, place);
+ break;
+ }
}
return result;
Index: m4/hash.h
===================================================================
RCS file: /sources/m4/m4/m4/hash.h,v
retrieving revision 1.11
diff -u -r1.11 hash.h
--- m4/hash.h 1 May 2005 11:10:05 -0000 1.11
+++ m4/hash.h 1 Sep 2006 23:01:45 -0000
@@ -1,5 +1,5 @@
/* GNU m4 -- A simple macro processor
- Copyright (C) 2001 Free Software Foundation, Inc.
+ Copyright (C) 2001, 2006 Free Software Foundation, Inc.
Written by Gary V. Vaughan <gary@xxxxxxx>
This program is free software; you can redistribute it and/or modify
@@ -71,6 +71,8 @@
extern void * m4_get_hash_iterator_value (m4_hash_iterator *place);
extern m4_hash_iterator *m4_get_hash_iterator_next (const m4_hash *hash,
m4_hash_iterator *place);
+extern void m4_free_hash_iterator (const m4_hash *hash,
+ m4_hash_iterator *place);
END_C_DECLS
Index: m4/m4.c
===================================================================
RCS file: /sources/m4/m4/m4/m4.c,v
retrieving revision 1.17
diff -u -r1.17 m4.c
--- m4/m4.c 25 Aug 2006 19:23:02 -0000 1.17
+++ m4/m4.c 1 Sep 2006 23:01:45 -0000
@@ -36,7 +36,7 @@
context->nesting_limit = DEFAULT_NESTING_LIMIT;
- context->search_path = xzalloc (sizeof context->search_path);
+ context->search_path = xzalloc (sizeof *context->search_path);
m4__include_init (context);
return context;
Index: m4/symtab.c
===================================================================
RCS file: /sources/m4/m4/m4/symtab.c,v
retrieving revision 1.57
diff -u -r1.57 symtab.c
--- m4/symtab.c 30 Aug 2006 04:25:42 -0000 1.57
+++ m4/symtab.c 1 Sep 2006 23:01:45 -0000
@@ -105,7 +105,10 @@
userdata);
if (result != NULL)
- break;
+ {
+ m4_free_hash_iterator (symtab->table, place);
+ break;
+ }
}
return result;
Index: tests/builtins.at
===================================================================
RCS file: /sources/m4/m4/tests/builtins.at,v
retrieving revision 1.16
diff -u -r1.16 builtins.at
--- tests/builtins.at 31 Aug 2006 03:21:35 -0000 1.16
+++ tests/builtins.at 1 Sep 2006 23:01:45 -0000
@@ -176,6 +176,7 @@
## --- ##
AT_SETUP([gmp])
+AT_KEYWORDS([module])
# cannot perform test without --with-gmp
AT_CHECK([test "$USE_GMP" = yes || exit 77])
Index: tests/modules.at
===================================================================
RCS file: /sources/m4/m4/tests/modules.at,v
retrieving revision 1.18
diff -u -r1.18 modules.at
--- tests/modules.at 31 Aug 2006 03:21:36 -0000 1.18
+++ tests/modules.at 1 Sep 2006 23:01:45 -0000
@@ -26,6 +26,7 @@
AT_SETUP([Freezing modules])
AT_KEYWORDS([frozen])
+AT_KEYWORDS([module])
AT_DATA([[frozen.m4]],
[[divert(1)dnl
@@ -134,6 +135,7 @@
## ------ ##
AT_SETUP([modules: shadow])
+AT_KEYWORDS([module])
AT_DATA([[input.m4]],
[[# no modules loaded yet
@@ -274,6 +276,7 @@
## ------ ##
AT_SETUP([modules: unload])
+AT_KEYWORDS([module])
AT_DATA([[input.m4]],
[[test
@@ -347,6 +350,7 @@
# from the first command:
AT_SETUP([modules: importing])
+AT_KEYWORDS([module])
AT_DATA([[input.m4]],
[[import
@@ -405,6 +409,7 @@
# memory and then redefined by a subsequent load.
AT_SETUP([modules: trace])
+AT_KEYWORDS([module])
AT_DATA([[input.m4]],
[[test
Next Message by Date:
click to view message preview
testsuite on mingw
-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA1
When cross-compiling from cygwin to mingw, the testsuite is created by
cygwin with \n line endings, but executed by a mingw executable which
produces \r\n line endings. I'm installing this to normalize mingw output
in the testsuite, so that it doesn't fail practically every test just
because of the line ending differences.
2006-09-04 Eric Blake <ebb9@xxxxxxx>
* doc/m4.texinfo (Changeword): Skip test on mingw, where the
native echo is braindead.
* checks/check-them (strip_needed): Ignore \r in output. Now the
testsuite will pass when cross-compiling from cygwin to mingw.
- --
Life is short - so eat dessert first!
Eric Blake ebb9@xxxxxxx
-----BEGIN PGP SIGNATURE-----
Version: GnuPG v1.4.2.1 (Cygwin)
Comment: Public key at home.comcast.net/~ericblake/eblake.gpg
Comment: Using GnuPG with Mozilla - http://enigmail.mozdev.org
iD8DBQFE/DXQ84KuGfSFAYARAu61AJ4/wVvZ+Kh65320ZfRzvLOs7kSqxACeP1Jh
oh0kmsMQT+tESUPvC0M5pUg=
=ZNpw
-----END PGP SIGNATURE-----
Index: checks/check-them
===================================================================
RCS file: /sources/m4/m4/checks/Attic/check-them,v
retrieving revision 1.1.1.1.2.11
diff -u -p -r1.1.1.1.2.11 check-them
--- checks/check-them 22 Aug 2006 19:56:09 -0000 1.1.1.1.2.11
+++ checks/check-them 4 Sep 2006 14:17:39 -0000
@@ -26,11 +26,22 @@ xout=$tmp/m4-xout
xerr=$tmp/m4-xerr
failed=
skipped=
+strip_needed=false
# Find out how the executable prints argv[0]
m4=`m4 --help | sed -e 's/Usage: \(.*\) \[OPTION.*/\1/' \
-e 's/\\\\/\\\\\\\\/g' -e 1q`
+# Find out if we should strip \r in the output
+m4 --version > $out
+m4 --version | tr -d '\015' > $xout
+if cmp -s $out $xout; then
+ :
+else
+ echo "Ignoring carriage returns"
+ strip_needed=:
+fi
+
# Find out where the examples live.
examples=.
if test "x$1" = x-I ; then
@@ -57,6 +68,19 @@ do
sed -e '/^dnl @result{}/!d' -e 's///' -e "s|\.\./examples|$examples|" \
"$file" > $xout
+ sed -e '/^dnl @error{}/!d' -e 's///' -e "s|^m4:|$m4:|" "$file" > $xerr
+
+ # For the benefit of mingw, normalize \r\n line endings
+ if $strip_needed ; then
+ tr -d '\015' < $out > $out.t
+ mv $out.t $out
+ tr -d '\015' < $xout > $xout.t
+ mv $xout.t $xout
+ tr -d '\015' < $err > $err.t
+ mv $err.t $err
+ tr -d '\015' < $xerr > $xerr.t
+ mv $xerr.t $xerr
+ fi
if cmp -s $out $xout; then
:
@@ -67,8 +91,6 @@ do
diff $xout $out
fi
- sed -e '/^dnl @error{}/!d' -e 's///' -e "s|^m4:|$m4:|" "$file" > $xerr
-
if cmp -s $err $xerr; then
:
else
Index: doc/m4.texinfo
===================================================================
RCS file: /sources/m4/m4/doc/m4.texinfo,v
retrieving revision 1.1.1.1.2.71
diff -u -p -r1.1.1.1.2.71 m4.texinfo
--- doc/m4.texinfo 4 Sep 2006 13:35:09 -0000 1.1.1.1.2.71
+++ doc/m4.texinfo 4 Sep 2006 14:17:40 -0000
@@ -2766,6 +2766,9 @@ the first of these is discarded before s
@example
ifdef(`changeword', `', `errprint(` skipping: no changeword support
')m4exit(`77')')dnl
+ifdef(`__unix__', ,
+ `errprint(` skipping: syscmd does not have unix semantics
+')m4exit(`77')')dnl
changecom(`/*', `*/')dnl
define(`foo', `bar')dnl
changeword(`#\([_a-zA-Z0-9]*\)')
_______________________________________________
M4-patches mailing list
M4-patches@xxxxxxx
http://lists.gnu.org/mailman/listinfo/m4-patches
Previous Message by Thread:
click to view message preview
plug some memory bugs
It's a wonder that CVS head even ran; it has had a latent bug since Oct 2003
that caused out-of-bounds writes to the heap on bootup, even before option
processing starts! I also narrowed down why this crashes:
$ echo 'define(bar,1)define(baz,2)renamesyms(^ba.,baa)'|m4
Our hash table is not prepared to handle multiple entries with equivalent keys
(and even documented this, but not very well - node_insert mentioned that
inserting equal keys should just shadow the older entry, but bucket_insert
mentioned that relative ordering is not preserved). When we go to traverse the
table for cleanup, we do a lookup for the shadowed "baa" key being visited by
the iterator, but find the other "baa" key in the table, and then try to delete
the element not pointed to by the iterator, which breaks the iteration. I
added some assertion framework that catches this and other hash-table no-nos.
This also fixes some potential memory leaks, if any hash table iterator
callback ever returns non-NULL. Now it is a question of how we should fix
renamesyms to quit mapping multiple macros to the same name, and ending up with
multiple entries in the hashtable; or add an API so that it is easier to delete
the element being visited by the iterator without having to repeat a key lookup
(and perhaps find the wrong entry); or something else altogether.
Meanwhile, we were compiling getopt twice. And finally, I consistently used
the keyword "module" for all tests in the testsuite that depend on dynamic
modules (and which fail when ./configure --disable-shared).
2006-09-01 Eric Blake <ebb9@xxxxxxx>
* m4/m4.c (m4_create): Fix latent bug since 2003-10-08.
* m4/hash.h (m4_free_hash_iterator): New prototype.
* m4/hash.c (struct m4_hash) [!NDEBUG]: Add iter member, to
ensure we don't do unsafe things while iterating.
(HASH_ITER, ITER_CHAIN): New accessor macros.
(m4_hash_new, m4_hash_resize, maybe_grow): Fix malloc typing bug.
(m4_hash_delete, m4_hash_insert): Unsafe while iterating.
(m4_hash_remove) [!NDEBUG]: Enforce safety while iterating.
(m4_get_hash_iterator_next) [!NDEBUG]: Track current iterators,
to catch unsafe actions.
(m4_free_hash_iterator): New function, to avoid memory leaks, and
when debugging, to track safe actions.
(m4_hash_apply): Avoid memory leak.
* m4/symtab.c (m4_symtab_apply): Likewise.
* ltdl/m4/gnulib-cache.m4: Remove getopt from here; it is pulled
in manually to src/ for now.
* tests/builtins.at (gmp): Add keyword module.
* tests/modules.at (Freezing modules, modules: shadow)
(modules: unload, modules: importing, modules: trace): Likewise.
Index: ltdl/m4/gnulib-cache.m4
===================================================================
RCS file: /sources/m4/m4/ltdl/m4/gnulib-cache.m4,v
retrieving revision 1.9
diff -u -r1.9 gnulib-cache.m4
--- ltdl/m4/gnulib-cache.m4 25 Aug 2006 19:23:02 -0000 1.9
+++ ltdl/m4/gnulib-cache.m4 1 Sep 2006 23:01:45 -0000
@@ -15,15 +15,17 @@
# Specification in the form of a command-line invocation:
-# gnulib-tool --import --dir=. --lib=libgnu --source-base=gnu --m4-
base=ltdl/m4 --doc-base=doc --aux-dir=ltdl/config --libtool --macro-prefix=M4
assert binary-io cloexec close-stream dirname error exit fdl filenamecat fopen-
safer free gendocs getopt gettext gnupload mkstemp obstack progname regex
regexprops-generic stdbool stdlib-safer strtol unlocked-io verror xalloc xalloc-
die xstrndup xvasprintf
+# gnulib-tool --import --dir=. --lib=libgnu --source-base=gnu --m4-
base=ltdl/m4 --doc-base=doc --aux-dir=ltdl/config --libtool --macro-prefix=M4
assert binary-io cloexec close-stream dirname error exit fdl filenamecat fopen-
safer free gendocs gettext gnupload mkstemp obstack progname regex regexprops-
generic stdbool stdlib-safer strtol unlocked-io verror xalloc xalloc-die
xstrndup xvasprintf
# Specification in the form of a few gnulib-tool.m4 macro invocations:
-gl_MODULES([assert binary-io cloexec close-stream dirname error exit fdl
filenamecat fopen-safer free gendocs getopt gettext gnupload mkstemp obstack
progname regex regexprops-generic stdbool stdlib-safer strtol unlocked-io
verror xalloc xalloc-die xstrndup xvasprintf])
+gl_LOCAL_DIR([])
+gl_MODULES([assert binary-io cloexec close-stream dirname error exit fdl
filenamecat fopen-safer free gendocs gettext gnupload mkstemp obstack progname
regex regexprops-generic stdbool stdlib-safer strtol unlocked-io verror xalloc
xalloc-die xstrndup xvasprintf])
gl_AVOID([])
gl_SOURCE_BASE([gnu])
gl_M4_BASE([ltdl/m4])
gl_DOC_BASE([doc])
gl_TESTS_BASE([tests])
gl_LIB([libgnu])
+gl_MAKEFILE_NAME([])
gl_LIBTOOL
gl_MACRO_PREFIX([M4])
Index: m4/hash.c
===================================================================
RCS file: /sources/m4/m4/m4/hash.c,v
retrieving revision 1.19
diff -u -r1.19 hash.c
--- m4/hash.c 28 Jul 2006 01:49:37 -0000 1.19
+++ m4/hash.c 1 Sep 2006 23:01:45 -0000
@@ -37,6 +37,9 @@
m4_hash_hash_func *hash_func;
m4_hash_cmp_func *cmp_func;
hash_node **buckets;
+#ifndef NDEBUG
+ m4_hash_iterator *iter; /* current iterator */
+#endif
};
struct hash_node
@@ -47,6 +50,17 @@
};
+struct m4_hash_iterator
+{
+ const m4_hash *hash; /* contains the buckets */
+ hash_node * place; /* the node we are about to return */
+ hash_node * next; /* the next node, incase PLACE is removed */
+ size_t next_bucket; /* the next bucket index following NEXT */
+#ifndef NDEBUG
+ m4_hash_iterator *chain; /* multiple iterators visiting one hash */
+#endif
+};
+
#define HASH_SIZE(hash) ((hash)->size)
#define HASH_LENGTH(hash) ((hash)->length)
@@ -58,13 +72,28 @@
#define NODE_KEY(node) ((node)->key)
#define NODE_VALUE(node) ((node)->value)
+#define ITERATOR_HASH(i) ((i)->hash)
+#define ITERATOR_PLACE(i) ((i)->place)
+#define ITERATOR_NEXT(i) ((i)->next)
+#define ITERATOR_NEXT_BUCKET(i) ((i)->next_bucket)
+
+#define ITERATOR_NEXT_NEXT(i) NODE_NEXT (ITERATOR_PLACE (i))
+
/* Helper macros. */
#define BUCKET_NTH(hash, n) (HASH_BUCKETS (hash)[n])
#define BUCKET_COUNT(hash, key) \
- ((*HASH_HASH_FUNC(hash))(key) % HASH_SIZE(hash))
+ ((*HASH_HASH_FUNC (hash))(key) % HASH_SIZE (hash))
#define BUCKET_KEY(hash, key) \
(BUCKET_NTH ((hash), BUCKET_COUNT ((hash), (key))))
+/* Debugging macros. */
+#ifdef NDEBUG
+# define HASH_ITER(hash) 0
+# define ITER_CHAIN(iter) 0
+#else
+# define HASH_ITER(hash) (((m4_hash *) hash)->iter)
+# define ITER_CHAIN(iter) ((iter)->chain)
+#endif
static void bucket_insert (m4_hash *hash, hash_node *bucket);
@@ -77,7 +106,7 @@
-static hash_node *free_list = 0;
+static hash_node *free_list = NULL;
@@ -99,9 +128,12 @@
hash = xmalloc (sizeof *hash);
HASH_SIZE (hash) = size;
HASH_LENGTH (hash) = 0;
- HASH_BUCKETS (hash) = xcalloc (size, sizeof HASH_BUCKETS (hash));
+ HASH_BUCKETS (hash) = xcalloc (size, sizeof *HASH_BUCKETS (hash));
HASH_HASH_FUNC (hash) = hash_func;
HASH_CMP_FUNC (hash) = cmp_func;
+#ifndef NDEBUG
+ HASH_ITER (hash) = NULL;
+#endif
return hash;
}
@@ -123,15 +155,17 @@
}
/* Recycle each of the nodes in HASH onto the free list, and release
- the rest of the memory used by the table. Memory addressed by
- the recycled nodes is _NOT_ freed: this needs to be done manually
- to prevent memory leaks. */
+ the rest of the memory used by the table. Memory addressed by the
+ recycled nodes is _NOT_ freed: this needs to be done manually to
+ prevent memory leaks. This is not safe to call while HASH is being
+ iterated. */
void
m4_hash_delete (m4_hash *hash)
{
size_t i;
assert (hash);
+ assert (!HASH_ITER (hash));
for (i = 0; i < HASH_SIZE (hash); ++i)
if (BUCKET_NTH (hash, i))
@@ -154,16 +188,16 @@
for (node = BUCKET_NTH (hash, i); node->next; node = NODE_NEXT (node))
{
- assert (NODE_KEY(node) == 0);
+ assert (NODE_KEY (node) == NULL);
--HASH_LENGTH (hash);
}
- assert (NODE_KEY(node) == 0);
+ assert (NODE_KEY (node) == NULL);
--HASH_LENGTH (hash);
NODE_NEXT (node) = free_list;
free_list = BUCKET_NTH (hash, i);
- BUCKET_NTH (hash, i) = 0;
+ BUCKET_NTH (hash, i) = NULL;
}
/* Create and initialise a new node with KEY and VALUE, by reusing a
@@ -171,7 +205,7 @@
static hash_node *
node_new (const void *key, void *value)
{
- hash_node *node = 0;
+ hash_node *node = NULL;
if (free_list)
{
@@ -185,7 +219,7 @@
assert (node);
- NODE_NEXT (node) = 0;
+ NODE_NEXT (node) = NULL;
NODE_KEY (node) = key;
NODE_VALUE (node) = value;
@@ -197,7 +231,7 @@
node_delete (m4_hash *hash, hash_node *node)
{
assert (node);
- assert (NODE_KEY(node) == 0);
+ assert (NODE_KEY (node) == NULL);
NODE_NEXT (node) = free_list;
free_list = node;
@@ -205,15 +239,18 @@
--HASH_LENGTH (hash);
}
-/* Create a new entry in HASH with KEY and VALUE, making use of
- nodes in the free list if possible, and potentially growing
- the size of the table if node density is too high. */
+/* Create a new entry in HASH with KEY and VALUE, making use of nodes
+ in the free list if possible, and potentially growing the size of
+ the table if node density is too high. This is not safe to call
+ while HASH is being iterated. Currently, it is not safe to call
+ this if another entry already matches KEY. */
const void *
m4_hash_insert (m4_hash *hash, const void *key, void *value)
{
hash_node *node;
assert (hash);
+ assert (!HASH_ITER (hash));
node = node_new (key, value);
node_insert (hash, node);
@@ -233,7 +270,7 @@
assert (hash);
assert (node);
- assert (NODE_NEXT (node) == 0);
+ assert (NODE_NEXT (node) == NULL);
n = BUCKET_COUNT (hash, NODE_KEY (node));
NODE_NEXT (node) = BUCKET_NTH (hash, n);
@@ -242,21 +279,32 @@
++HASH_LENGTH (hash);
}
-/* Remove from HASH, the first node with key KEY; comparing keys
- with HASH's cmp_func. Any nodes with the same KEY previously
- hidden by the removed node will become visible again. The key
- field of the removed node is returned. */
+/* Remove from HASH, the first node with key KEY; comparing keys with
+ HASH's cmp_func. Any nodes with the same KEY previously hidden by
+ the removed node will become visible again. The key field of the
+ removed node is returned, or the original KEY If there was no
+ match. This is unsafe if multiple iterators are visiting HASH, or
+ when a lone iterator is visiting on a different key. */
void *
m4_hash_remove (m4_hash *hash, const void *key)
{
size_t n;
+#ifndef NDEBUG
+ m4_hash_iterator *iter = HASH_ITER (hash);
+
assert (hash);
+ if (HASH_ITER (hash))
+ {
+ assert (!ITER_CHAIN (iter));
+ assert (ITERATOR_PLACE (iter));
+ }
+#endif
n = BUCKET_COUNT (hash, key);
{
- hash_node *node = 0;
+ hash_node *node = NULL;
do
{
@@ -271,7 +319,9 @@
key = NODE_KEY (next);
#ifndef NDEBUG
- NODE_KEY (next) = 0;
+ if (iter)
+ assert (ITERATOR_PLACE (iter) == next);
+ NODE_KEY (next) = NULL;
#endif
node_delete (hash, next);
break;
@@ -284,11 +334,12 @@
return (void *) key;
}
-/* Return the address of the value field of the first node in
- HASH that has a matching KEY. The address is returned so that
- an explicit 0 value can be distinguished from a failed lookup
- (also 0). Fortuitously for M4, this also means that the value
- field can be changed `in situ' to implement a value stack. */
+/* Return the address of the value field of the first node in HASH
+ that has a matching KEY. The address is returned so that an
+ explicit NULL value can be distinguished from a failed lookup (also
+ NULL). Fortuitously for M4, this also means that the value field
+ can be changed `in situ' to implement a value stack. Safe to call
+ even when an iterator is in force. */
void **
m4_hash_lookup (m4_hash *hash, const void *key)
{
@@ -298,7 +349,7 @@
node = node_lookup (hash, key);
- return node ? &NODE_VALUE (node) : 0;
+ return node ? &NODE_VALUE (node) : NULL;
}
/* Return the first node in HASH that has a matching KEY. */
@@ -317,7 +368,8 @@
return node;
}
-/* How many entries are currently contained by HASH. */
+/* How many entries are currently contained by HASH. Safe to call
+ even during an interation. */
size_t
m4_get_hash_length (m4_hash *hash)
{
@@ -334,7 +386,8 @@
to set a smaller or larger initial size if you know in advance what
order of magnitude of entries will be in the table. Be aware that
the efficiency of the lookup and grow features require that the size
- always be 1 less than a power of 2. */
+ always be 1 less than a power of 2. Unsafe if HASH is being visited
+ by an iterator. */
void
m4_hash_resize (m4_hash *hash, size_t size)
{
@@ -342,12 +395,13 @@
size_t original_size;
assert (hash);
+ assert (!HASH_ITER (hash));
original_size = HASH_SIZE (hash);
original_buckets = HASH_BUCKETS (hash);
HASH_SIZE (hash) = size;
- HASH_BUCKETS (hash) = xcalloc (size, sizeof HASH_BUCKETS (hash));
+ HASH_BUCKETS (hash) = xcalloc (size, sizeof *HASH_BUCKETS (hash));
{
size_t i;
@@ -377,8 +431,9 @@
hash_node **original_buckets = HASH_BUCKETS (hash);
/* HASH sizes are always 1 less than a power of 2. */
- HASH_SIZE (hash) = (2* (1+ original_size)) -1;
- HASH_BUCKETS (hash) = xcalloc (hash->size, sizeof HASH_BUCKETS (hash));
+ HASH_SIZE (hash) = (2 * (1 + original_size)) -1;
+ HASH_BUCKETS (hash) = xcalloc (HASH_SIZE (hash),
+ sizeof *HASH_BUCKETS (hash));
{
size_t i;
@@ -392,7 +447,8 @@
}
/* Insert each node in BUCKET into HASH. Relative ordering of nodes
- is not preserved. */
+ is not preserved. This would need to change if we were to
+ guarantee relative ordering within a bucket for identical keys. */
static void
bucket_insert (m4_hash *hash, hash_node *bucket)
{
@@ -404,7 +460,7 @@
hash_node *next = NODE_NEXT (bucket);
/* Break link to rest of the bucket before reinserting. */
- NODE_NEXT (bucket) = 0;
+ NODE_NEXT (bucket) = NULL;
node_insert (hash, bucket);
bucket = next;
@@ -412,6 +468,9 @@
while (bucket);
}
+/* Reclaim all memory used by free nodes. Safe to call at any time,
+ although only worth calling at program shutdown to verify no
+ leaks. */
void
m4_hash_exit (void)
{
@@ -425,21 +484,14 @@
-struct m4_hash_iterator
-{
- const m4_hash *hash; /* contains the buckets */
- hash_node * place; /* the node we are about to return */
- hash_node * next; /* the next node, incase PLACE is removed */
- size_t next_bucket; /* the next bucket index following NEXT */
-};
-
-#define ITERATOR_HASH(i) ((i)->hash)
-#define ITERATOR_PLACE(i) ((i)->place)
-#define ITERATOR_NEXT(i) ((i)->next)
-#define ITERATOR_NEXT_BUCKET(i) ((i)->next_bucket)
-
-#define ITERATOR_NEXT_NEXT(i) NODE_NEXT (ITERATOR_PLACE (i))
-
+/* Iterate over a given HASH. Start with PLACE being NULL, then
+ repeat with PLACE being the previous return value. The return
+ value is the current location of the iterator, or NULL when the
+ walk is complete. Call m4_free_hash_iterator to abort iteration.
+ During the iteration, it is safe to search the list, and if no
+ other iterator is active, it is safe to remove the key pointed to
+ by this iterator. All other actions that modify HASH are
+ unsafe. */
m4_hash_iterator *
m4_get_hash_iterator_next (const m4_hash *hash, m4_hash_iterator *place)
{
@@ -451,6 +503,10 @@
{
place = xzalloc (sizeof *place);
ITERATOR_HASH (place) = hash;
+#ifndef NDEBUG
+ ITER_CHAIN (place) = HASH_ITER (hash);
+ HASH_ITER (hash) = place;
+#endif
}
next:
@@ -465,7 +521,7 @@
{
/* Find the next non-empty bucket. */
while ((ITERATOR_NEXT_BUCKET (place) < HASH_SIZE (hash))
- && (BUCKET_NTH (hash, ITERATOR_NEXT_BUCKET (place)) == 0))
+ && (BUCKET_NTH (hash, ITERATOR_NEXT_BUCKET (place)) == NULL))
{
++ITERATOR_NEXT_BUCKET (place);
}
@@ -477,7 +533,7 @@
= BUCKET_NTH (hash, ITERATOR_NEXT_BUCKET (place));
}
else
- ITERATOR_NEXT (place) = 0;
+ ITERATOR_NEXT (place) = NULL;
/* Advance the `next' reference. */
++ITERATOR_NEXT_BUCKET (place);
@@ -486,8 +542,8 @@
/* If there are no more nodes to return, recycle the iterator memory. */
if (! (ITERATOR_PLACE (place) || ITERATOR_NEXT (place)))
{
- free (place);
- return 0;
+ m4_free_hash_iterator (hash, place);
+ return NULL;
}
/* On the first call we need to put the 1st node in PLACE and
@@ -500,6 +556,38 @@
return place;
}
+/* Clean up the iterator PLACE within HASH when aborting an iteration
+ early. */
+void
+m4_free_hash_iterator (const m4_hash *hash, m4_hash_iterator *place)
+{
+#ifndef NDEBUG
+ m4_hash_iterator *iter = NULL;
+ m4_hash_iterator *next;
+
+ assert (hash);
+ assert (place && (ITERATOR_HASH (place) == hash));
+
+ do
+ {
+ next = iter ? ITER_CHAIN (iter) : HASH_ITER (hash);
+ if (place == next)
+ {
+ if (iter)
+ ITER_CHAIN (iter) = ITER_CHAIN (next);
+ else
+ HASH_ITER (hash) = ITER_CHAIN (next);
+ break;
+ }
+ iter = next;
+ }
+ while (iter);
+ assert (next);
+#endif
+ free (place);
+}
+
+/* Return the key being visited by the iterator PLACE. */
const void *
m4_get_hash_iterator_key (m4_hash_iterator *place)
{
@@ -508,6 +596,7 @@
return NODE_KEY (ITERATOR_PLACE (place));
}
+/* Return the value being visited by the iterator PLACE. */
void *
m4_get_hash_iterator_value (m4_hash_iterator *place)
{
@@ -517,10 +606,12 @@
}
/* The following function is used for the cases where we want to do
- something to each and every entry in HASH. This function
- traverses the hash table, and calls a specified function FUNC for
- each entry in the table. FUNC is called with a pointer to the
- entry key, value, and the passed DATA argument. */
+ something to each and every entry in HASH. This function traverses
+ the hash table, and calls a specified function FUNC for each entry
+ in the table. FUNC is called with a pointer to the entry key,
+ value, and the passed DATA argument. If FUNC returns non-NULL,
+ abort the iteration and return that value; a return of NULL implies
+ success on all entries. */
void *
m4_hash_apply (m4_hash *hash, m4_hash_apply_func *func, void *userdata)
{
@@ -536,7 +627,10 @@
m4_get_hash_iterator_value (place), userdata);
if (result != NULL)
- break;
+ {
+ m4_free_hash_iterator (hash, place);
+ break;
+ }
}
return result;
Index: m4/hash.h
===================================================================
RCS file: /sources/m4/m4/m4/hash.h,v
retrieving revision 1.11
diff -u -r1.11 hash.h
--- m4/hash.h 1 May 2005 11:10:05 -0000 1.11
+++ m4/hash.h 1 Sep 2006 23:01:45 -0000
@@ -1,5 +1,5 @@
/* GNU m4 -- A simple macro processor
- Copyright (C) 2001 Free Software Foundation, Inc.
+ Copyright (C) 2001, 2006 Free Software Foundation, Inc.
Written by Gary V. Vaughan <gary@xxxxxxx>
This program is free software; you can redistribute it and/or modify
@@ -71,6 +71,8 @@
extern void * m4_get_hash_iterator_value (m4_hash_iterator *place);
extern m4_hash_iterator *m4_get_hash_iterator_next (const m4_hash *hash,
m4_hash_iterator *place);
+extern void m4_free_hash_iterator (const m4_hash *hash,
+ m4_hash_iterator *place);
END_C_DECLS
Index: m4/m4.c
===================================================================
RCS file: /sources/m4/m4/m4/m4.c,v
retrieving revision 1.17
diff -u -r1.17 m4.c
--- m4/m4.c 25 Aug 2006 19:23:02 -0000 1.17
+++ m4/m4.c 1 Sep 2006 23:01:45 -0000
@@ -36,7 +36,7 @@
context->nesting_limit = DEFAULT_NESTING_LIMIT;
- context->search_path = xzalloc (sizeof context->search_path);
+ context->search_path = xzalloc (sizeof *context->search_path);
m4__include_init (context);
return context;
Index: m4/symtab.c
===================================================================
RCS file: /sources/m4/m4/m4/symtab.c,v
retrieving revision 1.57
diff -u -r1.57 symtab.c
--- m4/symtab.c 30 Aug 2006 04:25:42 -0000 1.57
+++ m4/symtab.c 1 Sep 2006 23:01:45 -0000
@@ -105,7 +105,10 @@
userdata);
if (result != NULL)
- break;
+ {
+ m4_free_hash_iterator (symtab->table, place);
+ break;
+ }
}
return result;
Index: tests/builtins.at
===================================================================
RCS file: /sources/m4/m4/tests/builtins.at,v
retrieving revision 1.16
diff -u -r1.16 builtins.at
--- tests/builtins.at 31 Aug 2006 03:21:35 -0000 1.16
+++ tests/builtins.at 1 Sep 2006 23:01:45 -0000
@@ -176,6 +176,7 @@
## --- ##
AT_SETUP([gmp])
+AT_KEYWORDS([module])
# cannot perform test without --with-gmp
AT_CHECK([test "$USE_GMP" = yes || exit 77])
Index: tests/modules.at
===================================================================
RCS file: /sources/m4/m4/tests/modules.at,v
retrieving revision 1.18
diff -u -r1.18 modules.at
--- tests/modules.at 31 Aug 2006 03:21:36 -0000 1.18
+++ tests/modules.at 1 Sep 2006 23:01:45 -0000
@@ -26,6 +26,7 @@
AT_SETUP([Freezing modules])
AT_KEYWORDS([frozen])
+AT_KEYWORDS([module])
AT_DATA([[frozen.m4]],
[[divert(1)dnl
@@ -134,6 +135,7 @@
## ------ ##
AT_SETUP([modules: shadow])
+AT_KEYWORDS([module])
AT_DATA([[input.m4]],
[[# no modules loaded yet
@@ -274,6 +276,7 @@
## ------ ##
AT_SETUP([modules: unload])
+AT_KEYWORDS([module])
AT_DATA([[input.m4]],
[[test
@@ -347,6 +350,7 @@
# from the first command:
AT_SETUP([modules: importing])
+AT_KEYWORDS([module])
AT_DATA([[input.m4]],
[[import
@@ -405,6 +409,7 @@
# memory and then redefined by a subsequent load.
AT_SETUP([modules: trace])
+AT_KEYWORDS([module])
AT_DATA([[input.m4]],
[[test
Next Message by Thread:
click to view message preview
testsuite on mingw
-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA1
When cross-compiling from cygwin to mingw, the testsuite is created by
cygwin with \n line endings, but executed by a mingw executable which
produces \r\n line endings. I'm installing this to normalize mingw output
in the testsuite, so that it doesn't fail practically every test just
because of the line ending differences.
2006-09-04 Eric Blake <ebb9@xxxxxxx>
* doc/m4.texinfo (Changeword): Skip test on mingw, where the
native echo is braindead.
* checks/check-them (strip_needed): Ignore \r in output. Now the
testsuite will pass when cross-compiling from cygwin to mingw.
- --
Life is short - so eat dessert first!
Eric Blake ebb9@xxxxxxx
-----BEGIN PGP SIGNATURE-----
Version: GnuPG v1.4.2.1 (Cygwin)
Comment: Public key at home.comcast.net/~ericblake/eblake.gpg
Comment: Using GnuPG with Mozilla - http://enigmail.mozdev.org
iD8DBQFE/DXQ84KuGfSFAYARAu61AJ4/wVvZ+Kh65320ZfRzvLOs7kSqxACeP1Jh
oh0kmsMQT+tESUPvC0M5pUg=
=ZNpw
-----END PGP SIGNATURE-----
Index: checks/check-them
===================================================================
RCS file: /sources/m4/m4/checks/Attic/check-them,v
retrieving revision 1.1.1.1.2.11
diff -u -p -r1.1.1.1.2.11 check-them
--- checks/check-them 22 Aug 2006 19:56:09 -0000 1.1.1.1.2.11
+++ checks/check-them 4 Sep 2006 14:17:39 -0000
@@ -26,11 +26,22 @@ xout=$tmp/m4-xout
xerr=$tmp/m4-xerr
failed=
skipped=
+strip_needed=false
# Find out how the executable prints argv[0]
m4=`m4 --help | sed -e 's/Usage: \(.*\) \[OPTION.*/\1/' \
-e 's/\\\\/\\\\\\\\/g' -e 1q`
+# Find out if we should strip \r in the output
+m4 --version > $out
+m4 --version | tr -d '\015' > $xout
+if cmp -s $out $xout; then
+ :
+else
+ echo "Ignoring carriage returns"
+ strip_needed=:
+fi
+
# Find out where the examples live.
examples=.
if test "x$1" = x-I ; then
@@ -57,6 +68,19 @@ do
sed -e '/^dnl @result{}/!d' -e 's///' -e "s|\.\./examples|$examples|" \
"$file" > $xout
+ sed -e '/^dnl @error{}/!d' -e 's///' -e "s|^m4:|$m4:|" "$file" > $xerr
+
+ # For the benefit of mingw, normalize \r\n line endings
+ if $strip_needed ; then
+ tr -d '\015' < $out > $out.t
+ mv $out.t $out
+ tr -d '\015' < $xout > $xout.t
+ mv $xout.t $xout
+ tr -d '\015' < $err > $err.t
+ mv $err.t $err
+ tr -d '\015' < $xerr > $xerr.t
+ mv $xerr.t $xerr
+ fi
if cmp -s $out $xout; then
:
@@ -67,8 +91,6 @@ do
diff $xout $out
fi
- sed -e '/^dnl @error{}/!d' -e 's///' -e "s|^m4:|$m4:|" "$file" > $xerr
-
if cmp -s $err $xerr; then
:
else
Index: doc/m4.texinfo
===================================================================
RCS file: /sources/m4/m4/doc/m4.texinfo,v
retrieving revision 1.1.1.1.2.71
diff -u -p -r1.1.1.1.2.71 m4.texinfo
--- doc/m4.texinfo 4 Sep 2006 13:35:09 -0000 1.1.1.1.2.71
+++ doc/m4.texinfo 4 Sep 2006 14:17:40 -0000
@@ -2766,6 +2766,9 @@ the first of these is discarded before s
@example
ifdef(`changeword', `', `errprint(` skipping: no changeword support
')m4exit(`77')')dnl
+ifdef(`__unix__', ,
+ `errprint(` skipping: syscmd does not have unix semantics
+')m4exit(`77')')dnl
changecom(`/*', `*/')dnl
define(`foo', `bar')dnl
changeword(`#\([_a-zA-Z0-9]*\)')
_______________________________________________
M4-patches mailing list
M4-patches@xxxxxxx
http://lists.gnu.org/mailman/listinfo/m4-patches
|
|