1
Fork 0
mirror of https://git.savannah.gnu.org/git/guile.git synced 2025-05-21 04:00:19 +02:00

Allow arbitrary code in ASSOC procedures for weak hash tables (bug #29616).

* libguile/hashtab.c (struct t_assoc_args, do_weak_bucket_assoc):
  Remove.
  (struct t_fixup_args): New type.
  (do_weak_bucket_fixup): New function.
  (weak_bucket_assoc): Use it.  Keep strong references to BUCKET's
  entries in STRONG_REFS.  Call ASSOC once the alloc lock has been
  released.  This fixes bug #29616.

* test-suite/tests/weaks.test ("assoc can do anything"): New test.
This commit is contained in:
Ludovic Courtès 2010-10-11 15:38:06 +02:00
parent dff58577d8
commit e9bac3be61
2 changed files with 83 additions and 39 deletions

View file

@ -137,40 +137,35 @@ scm_fixup_weak_alist (SCM alist, size_t *removed_items)
|| (SCM_I_IS_VECTOR (table))) || (SCM_I_IS_VECTOR (table)))
/* Packed arguments for `do_weak_bucket_assoc ()'. */ /* Packed arguments for `do_weak_bucket_fixup'. */
struct t_assoc_args struct t_fixup_args
{ {
/* Input arguments. */ SCM bucket;
SCM object; SCM *bucket_copy;
SCM buckets;
size_t bucket_index;
scm_t_assoc_fn assoc_fn;
void *closure;
/* Output arguments. */
SCM result;
size_t removed_items; size_t removed_items;
}; };
static void * static void *
do_weak_bucket_assoc (void *data) do_weak_bucket_fixup (void *data)
{ {
struct t_assoc_args *args; struct t_fixup_args *args;
size_t removed; SCM pair, *copy;
SCM bucket, result;
args = (struct t_assoc_args *) data; args = (struct t_fixup_args *) data;
bucket = SCM_SIMPLE_VECTOR_REF (args->buckets, args->bucket_index); args->bucket = scm_fixup_weak_alist (args->bucket, &args->removed_items);
bucket = scm_fixup_weak_alist (bucket, &removed);
SCM_SIMPLE_VECTOR_SET (args->buckets, args->bucket_index, bucket); for (pair = args->bucket, copy = args->bucket_copy;
scm_is_pair (pair);
pair = SCM_CDR (pair), copy += 2)
{
/* At this point, all weak pairs have been removed. */
assert (!SCM_WEAK_PAIR_DELETED_P (SCM_CAR (pair)));
/* Run ASSOC_FN on the now clean BUCKET. */ /* Copy the key and value. */
result = args->assoc_fn (args->object, bucket, args->closure); copy[0] = SCM_CAAR (pair);
copy[1] = SCM_CDAR (pair);
args->result = result; }
args->removed_items = removed;
return args; return args;
} }
@ -184,26 +179,38 @@ weak_bucket_assoc (SCM table, SCM buckets, size_t bucket_index,
scm_t_assoc_fn assoc, SCM object, void *closure) scm_t_assoc_fn assoc, SCM object, void *closure)
{ {
SCM result; SCM result;
struct t_assoc_args args; SCM bucket, *strong_refs;
struct t_fixup_args args;
args.object = object; bucket = SCM_SIMPLE_VECTOR_REF (buckets, bucket_index);
args.buckets = buckets;
args.bucket_index = bucket_index;
args.assoc_fn = assoc;
args.closure = closure;
/* Fixup the bucket and pass the clean bucket to ASSOC. Do that with the /* Prepare STRONG_REFS as an array large enough to hold all the keys
allocation lock held to avoid seeing disappearing links pointing to and values in BUCKET. */
objects that have already been reclaimed (this happens when the strong_refs = alloca (scm_ilength (bucket) * 2 * sizeof (SCM));
disappearing links that point to it haven't yet been cleared.)
Thus, ASSOC must not take long, and it must not make any non-local
exit. */
GC_call_with_alloc_lock (do_weak_bucket_assoc, &args);
result = args.result; args.bucket = bucket;
args.bucket_copy = strong_refs;
/* Fixup BUCKET. Do that with the allocation lock held to avoid
seeing disappearing links pointing to objects that have already
been reclaimed (this happens when the disappearing links that point
to it haven't yet been cleared.)
The `do_weak_bucket_fixup' call populates STRONG_REFS with a copy
of BUCKET's entries after it's been fixed up. Thus, all the
entries kept in BUCKET are still reachable when ASSOC sees
them. */
GC_call_with_alloc_lock (do_weak_bucket_fixup, &args);
bucket = args.bucket;
SCM_SIMPLE_VECTOR_SET (buckets, bucket_index, bucket);
result = assoc (object, bucket, closure);
assert (!scm_is_pair (result) || assert (!scm_is_pair (result) ||
!SCM_WEAK_PAIR_DELETED_P (GC_is_visible (result))); !SCM_WEAK_PAIR_DELETED_P (GC_is_visible (result)));
scm_remember_upto_here_1 (strong_refs);
if (args.removed_items > 0 && SCM_HASHTABLE_P (table)) if (args.removed_items > 0 && SCM_HASHTABLE_P (table))
{ {
/* Update TABLE's item count and optionally trigger a rehash. */ /* Update TABLE's item count and optionally trigger a rehash. */

View file

@ -206,4 +206,41 @@
'("is" "test" "the" "weak" "hash system")) '("is" "test" "the" "weak" "hash system"))
(any not values) (any not values)
(hash-ref z test-key) (hash-ref z test-key)
#t)))))) #t))))
(pass-if "assoc can do anything"
;; Until 1.9.12, as hash table's custom ASSOC procedure was
;; called with the GC lock alloc held, which imposed severe
;; restrictions on what it could do (bug #29616). This test
;; makes sure this is no longer the case.
(let ((h (make-doubly-weak-hash-table 2))
(c 123)
(k "GNU"))
(define (assoc-ci key bucket)
(make-list 123) ;; this should be possible
(gc) ;; this too
(find (lambda (p)
(string-ci=? key (car p)))
bucket))
(hashx-set! string-hash-ci assoc-ci h
(string-copy "hello") (string-copy "world"))
(hashx-set! string-hash-ci assoc-ci h
k "Guile")
(and (every (cut valid? <> "Guile")
(unfold (cut >= <> c)
(lambda (_)
(hashx-ref string-hash-ci assoc-ci
h "gnu"))
1+
0))
(every (cut valid? <> "world")
(unfold (cut >= <> c)
(lambda (_)
(hashx-ref string-hash-ci assoc-ci
h "HELLO"))
1+
0))
#t)))))