It's illegal for the user-supplied function to modify the hash table
while executing hash_map*() methods. snap_file() may add elements
due to EXTRA_PREREQS, so we can't call it via hash_map_args().
Diagnosis and test by Shim Manning <shimmanning@gmail.com>
Possible fix by Dmitry Goncharov <dgoncharov@users.sf.net>
* src/hash.h (hash_table): Remember when we're in hash_map*().
* src/hash.c (hash_init): Initialize the in-map value to false.
(hash_map): Set the in-map value to true when walking the map.
(hash_map_arg): Ditto.
(hash_insert_at): Assert we're not in hash_map*()
(hash_free_items): Ditto.
(hash_delete_items): Ditto.
(hash_free): Ditto.
* src/file.c (snap_deps): Use hash_dump() to create a copy of the
hash and walk that to call snap_file(), so we can add more files.
(snap_file): Since we're calling it directly, don't use void* args.
/* Perform per-file snap operations. */
static void
/* Perform per-file snap operations. */
static void
-snap_file (const void *item, void *arg)
+snap_file (struct file *f, const struct dep *deps)
- struct file *f = (struct file*)item;
struct dep *prereqs = NULL;
struct dep *d;
struct dep *prereqs = NULL;
struct dep *d;
}
}
else if (f->is_target)
}
}
else if (f->is_target)
- prereqs = copy_dep_chain (arg);
+ prereqs = copy_dep_chain (deps);
{
struct dep *prereqs = expand_extra_prereqs (lookup_variable (STRING_SIZE_TUPLE(".EXTRA_PREREQS")));
{
struct dep *prereqs = expand_extra_prereqs (lookup_variable (STRING_SIZE_TUPLE(".EXTRA_PREREQS")));
- /* Perform per-file snap operations. */
- hash_map_arg(&files, snap_file, prereqs);
+ /* Perform per-file snap operations.
+ We can't use hash_map*() here because snap_file may add new elements
+ into the files hash, which is not allowed in the map. Instead make a
+ dump of the files and walk through that. */
+ void** filedump = hash_dump (&files, NULL, 0);
+ for (void** filep = filedump; *filep; ++filep)
+ snap_file (*filep, prereqs);
+
+ free (filedump);
free_dep_chain (prereqs);
}
free_dep_chain (prereqs);
}
ht->ht_collisions = 0;
ht->ht_lookups = 0;
ht->ht_rehashes = 0;
ht->ht_collisions = 0;
ht->ht_lookups = 0;
ht->ht_rehashes = 0;
ht->ht_hash_1 = hash_1;
ht->ht_hash_2 = hash_2;
ht->ht_compare = hash_cmp;
ht->ht_hash_1 = hash_1;
ht->ht_hash_2 = hash_2;
ht->ht_compare = hash_cmp;
hash_insert_at (struct hash_table *ht, const void *item, const void *slot)
{
const void *old_item = *(void **) slot;
hash_insert_at (struct hash_table *ht, const void *item, const void *slot)
{
const void *old_item = *(void **) slot;
+
+ /* It's illegal to insert while in hash_map*(). */
+ assert (! ht->ht_in_map);
+
if (HASH_VACANT (old_item))
{
ht->ht_fill++;
if (HASH_VACANT (old_item))
{
ht->ht_fill++;
{
void **vec = ht->ht_vec;
void **end = &vec[ht->ht_size];
{
void **vec = ht->ht_vec;
void **end = &vec[ht->ht_size];
+
+ /* It's illegal to free items while in hash_map*(). */
+ assert (! ht->ht_in_map);
+
for (; vec < end; vec++)
{
void *item = *vec;
for (; vec < end; vec++)
{
void *item = *vec;
{
void **vec = ht->ht_vec;
void **end = &vec[ht->ht_size];
{
void **vec = ht->ht_vec;
void **end = &vec[ht->ht_size];
+
+ /* It's illegal to delete all items while in hash_map*(). */
+ assert (! ht->ht_in_map);
+
for (; vec < end; vec++)
*vec = 0;
ht->ht_fill = 0;
for (; vec < end; vec++)
*vec = 0;
ht->ht_fill = 0;
void
hash_free (struct hash_table *ht, int free_items)
{
void
hash_free (struct hash_table *ht, int free_items)
{
+ /* It's illegal to free while in hash_map*(). */
+ assert (! ht->ht_in_map);
+
if (free_items)
hash_free_items (ht);
else
if (free_items)
hash_free_items (ht);
else
void **slot;
void **end = &ht->ht_vec[ht->ht_size];
void **slot;
void **end = &ht->ht_vec[ht->ht_size];
for (slot = ht->ht_vec; slot < end; slot++)
{
if (!HASH_VACANT (*slot))
(*map) (*slot);
}
for (slot = ht->ht_vec; slot < end; slot++)
{
if (!HASH_VACANT (*slot))
(*map) (*slot);
}
void **slot;
void **end = &ht->ht_vec[ht->ht_size];
void **slot;
void **end = &ht->ht_vec[ht->ht_size];
for (slot = ht->ht_vec; slot < end; slot++)
{
if (!HASH_VACANT (*slot))
(*map) (*slot, arg);
}
for (slot = ht->ht_vec; slot < end; slot++)
{
if (!HASH_VACANT (*slot))
(*map) (*slot, arg);
}
}
/* Double the size of the hash table in the event of overflow... */
}
/* Double the size of the hash table in the event of overflow... */
unsigned long ht_collisions; /* # of failed calls to comparison function */
unsigned long ht_lookups; /* # of queries */
unsigned int ht_rehashes; /* # of times we've expanded table */
unsigned long ht_collisions; /* # of failed calls to comparison function */
unsigned long ht_lookups; /* # of queries */
unsigned int ht_rehashes; /* # of times we've expanded table */
+ unsigned int ht_in_map:1; /* 1 if we're inside a hash_map*() function */
};
typedef int (*qsort_cmp_t) __P((void const *, void const *));
};
typedef int (*qsort_cmp_t) __P((void const *, void const *));
',
'', "hello.x.d\nhello.x\nworld.x.d\nworld.x\n");
',
'', "hello.x.d\nhello.x\nworld.x.d\nworld.x\n");
+# SV 67046. Rehashing of hash table files during iteration.
+my $files = "";
+for my $k (1 .. 1000) {
+ $files = "$files file$k";
+}
+run_make_test(qq!
+all: .EXTRA_PREREQS := $files
+all:;
+file%:;
+!,
+ '', "#MAKE#: 'all' is up to date.");
-
-# This tells the test driver that the perl test script executed properly.