diff --git a/manual/Unit srfi-69 b/manual/Unit srfi-69 index 92db700..9db2a97 100644 --- a/manual/Unit srfi-69 +++ b/manual/Unit srfi-69 @@ -13,7 +13,7 @@ CHICKEN implements SRFI 69 with SRFI 90 extensions. For more information, see ==== make-hash-table -(make-hash-table [TEST HASH SIZE] [#:test TEST] [#:hash HASH] [#:size SIZE] [#:initial INITIAL] [#:min-load MIN-LOAD] [#:max-load MAX-LOAD] [#:weak-keys WEAK-KEYS] [#:weak-values WEAK-VALUES]) +(make-hash-table [TEST HASH SIZE] [#:test TEST] [#:hash HASH] [#:size SIZE] [#:initial INITIAL] [#:randomization RANDOMIZATION] [#:min-load MIN-LOAD] [#:max-load MAX-LOAD] [#:weak-keys WEAK-KEYS] [#:weak-values WEAK-VALUES]) Returns a new {{HASH-TABLE}} with the supplied configuration. @@ -21,6 +21,7 @@ Returns a new {{HASH-TABLE}} with the supplied configuration. ; {{HASH}} : The hash function. ; {{SIZE}} : The expected number of table elements. ; {{INITIAL}} : The default initial value. +; {{RANDOMIZATION}} : A value for perturbing hash values. Should never be a fixed value! ; {{MIN-LOAD}} : The minimum load factor. A {{flonum}} in (0.0 1.0). ; {{MAX-LOAD}} : The maximum load factor. A {{flonum}} in (0.0 1.0). ; {{WEAK-KEYS}} : Use weak references for keys. (Ignored) @@ -29,7 +30,7 @@ Returns a new {{HASH-TABLE}} with the supplied configuration. ==== alist->hash-table -(alist->hash-table A-LIST [#:test TEST] [#:hash HASH] [#:size SIZE] [#:initial INITIAL] [#:min-load MIN-LOAD] [#:max-load MAX-LOAD] [#:weak-keys WEAK-KEYS] [#:weak-values WEAK-VALUES]) +(alist->hash-table A-LIST [#:test TEST] [#:hash HASH] [#:size SIZE] [#:initial INITIAL] [#:randomization RANDOMIZATION] [#:min-load MIN-LOAD] [#:max-load MAX-LOAD] [#:weak-keys WEAK-KEYS] [#:weak-values WEAK-VALUES]) Returns a new {{HASH-TABLE}}. The {{HASH-TABLE}} is populated from the {{A-LIST}}. The keyword arguments are per {{make-hash-table}}. @@ -104,6 +105,13 @@ Does the {{HASH-TABLE}} have a default initial value? The {{HASH-TABLE}} default initial value. +==== hash-table-randomization + +(hash-table-randomization HASH-TABLE) + +The randomization number for {{HASH-TABLE}}. Make sure you never +expose this to a potential attacker. + ==== hash-table-keys @@ -285,38 +293,46 @@ entry. All hash functions return a {{fixnum}} in the range [0 {{BOUND}}). +When given the fixnum RANDOMIZATION, these functions will use this +to perturb the value; if not specified, the value will differ for +each invocation of your program. This is for security reasons; an +attacker who knows what a value hashes to can deliberately try to +cause collisions, thereby flattening your hash table, effectively +reducing it to a list. Always make sure you don't expose any +hashed value to an attacker. + ==== number-hash -(number-hash NUMBER [BOUND]) +(number-hash NUMBER [BOUND RANDOMIZATION]) For use with {{=}} as a {{hash-table-equivalence-function}}. ==== object-uid-hash -(object-uid-hash OBJECT [BOUND]) +(object-uid-hash OBJECT [BOUND RANDOMIZATION]) Currently a synonym for {{equal?-hash}}. ==== symbol-hash -(symbol-hash SYMBOL [BOUND]) +(symbol-hash SYMBOL [BOUND RANDOMIZATION]) For use with {{eq?}} as a {{hash-table-equivalence-function}}. ==== keyword-hash -(keyword-hash KEYWORD [BOUND]) +(keyword-hash KEYWORD [BOUND RANDOMIZATION]) For use with {{eq?}} as a {{hash-table-equivalence-function}}. ==== string-hash -(string-hash STRING [BOUND START END]) +(string-hash STRING [BOUND START END RANDOMIZATION]) For use with {{string=?}} as a {{hash-table-equivalence-function}}. The optional {{START}} and {{END}} arguments may be given to limit @@ -325,43 +341,43 @@ the hash calculation to a specific sub-section of {{STRING}}. ==== string-ci-hash -(string-hash-ci STRING [BOUND START END])
-(string-ci-hash STRING [BOUND START END]) +(string-hash-ci STRING [BOUND START END RANDOMIZATION])
+(string-ci-hash STRING [BOUND START END RANDOMIZATION]) For use with {{string-ci=?}} as a {{hash-table-equivalence-function}}. ==== eq?-hash -(eq?-hash OBJECT [BOUND]) +(eq?-hash OBJECT [BOUND RANDOMIZATION]) For use with {{eq?}} as a {{hash-table-equivalence-function}}. ==== eqv?-hash -(eqv?-hash OBJECT [BOUND]) +(eqv?-hash OBJECT [BOUND RANDOMIZATION]) For use with {{eqv?}} as a {{hash-table-equivalence-function}}. ==== equal?-hash -(equal?-hash OBJECT [BOUND]) +(equal?-hash OBJECT [BOUND RANDOMIZATION]) For use with {{equal?}} as a {{hash-table-equivalence-function}}. ==== hash -(hash OBJECT [BOUND]) +(hash OBJECT [BOUND RANDOMIZATION]) Synonym for {{equal?-hash}}. ==== hash-by-identity -(hash-by-identity OBJECT [BOUND]) +(hash-by-identity OBJECT [BOUND RANDOMIZATION]) Synonym for {{eq?-hash}}. diff --git a/srfi-69.scm b/srfi-69.scm index febfe7e..f4c630e 100644 --- a/srfi-69.scm +++ b/srfi-69.scm @@ -105,16 +105,18 @@ (define-constant unknown-immediate-hash-value 262) (define-constant hash-default-bound 536870912) +(define hash-default-randomization + (##core#inline "C_random_fixnum" hash-default-bound)) ;; Force Hash to Bounded Fixnum: (define-inline (%fxabs fxn) (if (fx< fxn 0) (fxneg fxn) fxn ) ) -(define-inline (%hash/limit hsh lim) +(define-inline (%hash/limit hsh lim rnd) ;; use 32-bit mask to have identical hashes on 64-bit platforms (fxmod (fxand (foreign-value "C_MOST_POSITIVE_32_BIT_FIXNUM" int) - (%fxabs hsh)) + (fxxor (%fxabs hsh) rnd)) lim) ) ;; Number Hash: @@ -147,11 +149,12 @@ (cond [(fixnum? obj) obj] [else (%non-fixnum-number-hash obj)] ) ) -(define (number-hash obj #!optional (bound hash-default-bound)) +(define (number-hash obj #!optional (bound hash-default-bound) + (randomization hash-default-randomization)) (unless (number? obj) (##sys#signal-hook #:type 'number-hash "invalid number" obj) ) (##sys#check-exact bound 'number-hash) - (%hash/limit (%number-hash obj) bound) ) + (%hash/limit (%number-hash obj) bound randomization) ) ;; Object UID Hash: @@ -162,9 +165,10 @@ (define-inline (%object-uid-hash obj) (*equal?-hash obj) ) -(define (object-uid-hash obj #!optional (bound hash-default-bound)) +(define (object-uid-hash obj #!optional (bound hash-default-bound) + (randomization hash-default-randomization)) (##sys#check-exact bound 'object-uid-hash) - (%hash/limit (%object-uid-hash obj) bound) ) + (%hash/limit (%object-uid-hash obj) bound randomization) ) ;; Symbol Hash: @@ -175,10 +179,11 @@ (define-inline (%symbol-hash obj) (%string-hash (##sys#slot obj 1)) ) -(define (symbol-hash obj #!optional (bound hash-default-bound)) +(define (symbol-hash obj #!optional (bound hash-default-bound) + (randomization hash-default-randomization)) (##sys#check-symbol obj 'symbol-hash) (##sys#check-exact bound 'symbol-hash) - (%hash/limit (%symbol-hash obj) bound) ) + (%hash/limit (%symbol-hash obj) bound randomization) ) ;; Keyword Hash: @@ -195,10 +200,11 @@ (define-inline (%keyword-hash obj) (%string-hash (##sys#slot obj 1)) ) -(define (keyword-hash obj #!optional (bound hash-default-bound)) +(define (keyword-hash obj #!optional (bound hash-default-bound) + (randomization hash-default-randomization)) (##sys#check-keyword obj 'keyword-hash) (##sys#check-exact bound 'keyword-hash) - (%hash/limit (%keyword-hash obj) bound) ) + (%hash/limit (%keyword-hash obj) bound randomization) ) ;; Eq Hash: @@ -221,9 +227,10 @@ [(%immediate? obj) unknown-immediate-hash-value] [else (%object-uid-hash obj) ] ) ) -(define (eq?-hash obj #!optional (bound hash-default-bound)) +(define (eq?-hash obj #!optional (bound hash-default-bound) + (randomization hash-default-randomization)) (##sys#check-exact bound 'eq?-hash) - (%hash/limit (*eq?-hash obj) bound) ) + (%hash/limit (*eq?-hash obj) bound randomization) ) (define hash-by-identity eq?-hash) @@ -247,9 +254,10 @@ [(%immediate? obj) unknown-immediate-hash-value] [else (%object-uid-hash obj) ] ) ) -(define (eqv?-hash obj #!optional (bound hash-default-bound)) +(define (eqv?-hash obj #!optional (bound hash-default-bound) + (randomization hash-default-randomization)) (##sys#check-exact bound 'eqv?-hash) - (%hash/limit (*eqv?-hash obj) bound) ) + (%hash/limit (*eqv?-hash obj) bound randomization) ) ;; Equal Hash: @@ -327,37 +335,38 @@ ; (recursive-hash obj 0) ) -(define (equal?-hash obj #!optional (bound hash-default-bound)) +(define (equal?-hash obj #!optional (bound hash-default-bound) + (randomization hash-default-randomization)) (##sys#check-exact bound 'hash) - (%hash/limit (*equal?-hash obj) bound) ) + (%hash/limit (*equal?-hash obj) bound randomization) ) (define hash equal?-hash) ;; String Hash: -(define (string-hash str #!optional (bound hash-default-bound) . start+end) +(define (string-hash str #!optional (bound hash-default-bound) start end + (randomization hash-default-randomization)) (##sys#check-string str 'string-hash) (##sys#check-exact bound 'string-hash) - (let ((str (if (pair? start+end) - (let-optionals start+end ((start 0) - (end (##sys#size str))) - (##sys#check-range start 0 (##sys#size str) 'string-hash) - (##sys#check-range end 0 (##sys#size str) 'string-hash) - (##sys#substring str start end) ) - str) ) ) - (%hash/limit (%string-hash str) bound) ) ) - -(define (string-ci-hash str #!optional (bound hash-default-bound) . start+end) + (let ((str (if start + (let ((end (or end (##sys#size str)))) + (##sys#check-range start 0 (##sys#size str) 'string-hash) + (##sys#check-range end 0 (##sys#size str) 'string-hash) + (##sys#substring str start end)) + str)) ) + (%hash/limit (%string-hash str) bound randomization) ) ) + +(define (string-ci-hash str #!optional (bound hash-default-bound) start end + (randomization hash-default-randomization)) (##sys#check-string str 'string-ci-hash) (##sys#check-exact bound 'string-ci-hash) - (let ((str (if (pair? start+end) - (let-optionals start+end ((start 0) - (end (##sys#size str))) - (##sys#check-range start 0 (##sys#size str) 'string-hash-ci) - (##sys#check-range end 0 (##sys#size str) 'string-hash-ci) - (##sys#substring str start end) ) - str) ) ) - (%hash/limit (%string-ci-hash str) bound) ) ) + (let ((str (if start + (let ((end (or end (##sys#size str)))) + (##sys#check-range start 0 (##sys#size str) 'string-hash) + (##sys#check-range end 0 (##sys#size str) 'string-hash) + (##sys#substring str start end)) + str)) ) + (%hash/limit (%string-ci-hash str) bound randomization) ) ) (define string-hash-ci string-ci-hash) @@ -405,16 +414,17 @@ (define *make-hash-table (let ([make-vector make-vector]) (lambda (test hash len min-load max-load weak-keys weak-values initial - #!optional (vec (make-vector len '()))) + randomization #!optional (vec (make-vector len '()))) (##sys#make-structure 'hash-table - vec 0 test hash min-load max-load #f #f initial) ) ) ) + vec 0 test hash min-load max-load #f #f initial randomization) ) ) ) ;; SRFI-69 & SRFI-90'ish. ;; ;; Argument list is the pattern ;; ;; (make-hash-table #!optional test hash size -;; #!key test hash size initial min-load max-load weak-keys weak-values) +;; #!key test hash size initial randomization +;; min-load max-load weak-keys weak-values) ;; ;; where a keyword argument takes precedence over the corresponding optional ;; argument. Keyword arguments MUST come after optional & required @@ -435,6 +445,7 @@ [hash #f] [size hash-table-default-length] [initial #f] + [randomization (##core#inline "C_random_fixnum" hash-default-bound)] [min-load hash-table-default-min-load] [max-load hash-table-default-max-load] [weak-keys #f] @@ -501,6 +512,9 @@ (set! size (fxmin hash-table-max-length val))] [(#:initial) (set! initial (lambda () val))] + [(#:randomization) + (##sys#check-exact val 'make-hash-table) + (set! randomization val)] [(#:min-load) (##sys#check-inexact val 'make-hash-table) (unless (and (fp< 0.0 val) (fp< val 1.0)) @@ -533,7 +547,8 @@ (warning 'make-hash-table "user test without user hash") (set! hash equal?-hash) ) ) ) ) ; Done - (*make-hash-table test hash size min-load max-load weak-keys weak-values initial) ) ) ) ) ) + (*make-hash-table test hash size min-load max-load + weak-keys weak-values initial randomization) ) ) ) ) ) ;; Hash-Table Predicate: @@ -580,9 +595,13 @@ (and-let* ([thunk (##sys#slot ht 9)]) (thunk) ) ) +(define (hash-table-randomization ht) + (##sys#check-structure ht 'hash-table 'hash-table-initial) + (##sys#slot ht 10) ) + ;; hash-table-rehash!: -(define (hash-table-rehash! vec1 vec2 hash) +(define (hash-table-rehash! vec1 vec2 hash rnd) (let ([len1 (##sys#size vec1)] [len2 (##sys#size vec2)] ) (do ([i 0 (fx+ i 1)]) @@ -591,7 +610,7 @@ (unless (null? bucket) (let* ([pare (##sys#slot bucket 0)] [key (##sys#slot pare 0)] - [hshidx (hash key len2)] ) + [hshidx (hash key len2 rnd)] ) (##sys#setslot vec2 hshidx (cons (cons key (##sys#slot pare 1)) (##sys#slot vec2 hshidx))) (loop (##sys#slot bucket 1)) ) ) ) ) ) ) @@ -602,7 +621,7 @@ (let* ([deslen (fxmin hash-table-max-length (fx* len hash-table-new-length-factor))] [newlen (hash-table-canonical-length hash-table-prime-lengths deslen)] [vec2 (make-vector newlen '())] ) - (hash-table-rehash! vec vec2 (##sys#slot ht 4)) + (hash-table-rehash! vec vec2 (##sys#slot ht 4) (##sys#slot ht 10)) (##sys#setslot ht 1 vec2) ) ) ;; hash-table-check-resize!: @@ -633,7 +652,7 @@ (##sys#slot ht 2) (##sys#slot ht 5) (##sys#slot ht 6) (##sys#slot ht 7) (##sys#slot ht 8) - (##sys#slot ht 9) + (##sys#slot ht 9) (##sys#slot ht 10) vec2)] (##sys#setslot vec2 i (let copy-loop ([bucket (##sys#slot vec1 i)]) @@ -671,9 +690,10 @@ (hash-table-check-resize! ht newsiz) (let ([hash (##sys#slot ht 4)] [test (##sys#slot ht 3)] - [vec (##sys#slot ht 1)] ) + [vec (##sys#slot ht 1)] + [rnd (##sys#slot ht 10)]) (let* ([len (##sys#size vec)] - [hshidx (hash key len)] + [hshidx (hash key len rnd)] [bucket0 (##sys#slot vec hshidx)] ) (if (eq? core-eq? test) ; Fast path (eq? is rewritten by the compiler): @@ -710,9 +730,10 @@ (hash-table-check-resize! ht newsiz) (let ([hash (##sys#slot ht 4)] [test (##sys#slot ht 3)] - [vec (##sys#slot ht 1)] ) + [vec (##sys#slot ht 1)] + [rnd (##sys#slot ht 10)]) (let* ([len (##sys#size vec)] - [hshidx (hash key len)] + [hshidx (hash key len rnd)] [bucket0 (##sys#slot vec hshidx)] ) (if (eq? core-eq? test) ; Fast path (eq? is rewritten by the compiler): @@ -755,9 +776,10 @@ (hash-table-check-resize! ht newsiz) (let ([hash (##sys#slot ht 4)] [test (##sys#slot ht 3)] - [vec (##sys#slot ht 1)] ) + [vec (##sys#slot ht 1)] + [rnd (##sys#slot ht 10)]) (let* ([len (##sys#size vec)] - [hshidx (hash key len)] + [hshidx (hash key len rnd)] [bucket0 (##sys#slot vec hshidx)] ) (if (eq? core-eq? test) ; Fast path (eq? is rewritten by the compiler): @@ -794,9 +816,10 @@ (##sys#check-structure ht 'hash-table 'hash-table-ref) (##sys#check-closure def 'hash-table-ref) (let ([vec (##sys#slot ht 1)] - [test (##sys#slot ht 3)] ) + [test (##sys#slot ht 3)] + [rnd (##sys#slot ht 10)]) (let* ([hash (##sys#slot ht 4)] - [hshidx (hash key (##sys#size vec))] ) + [hshidx (hash key (##sys#size vec) rnd)] ) (if (eq? core-eq? test) ; Fast path (eq? is rewritten by the compiler): (let loop ([bucket (##sys#slot vec hshidx)]) @@ -822,9 +845,10 @@ (lambda (ht key def) (##sys#check-structure ht 'hash-table 'hash-table-ref/default) (let ([vec (##sys#slot ht 1)] - [test (##sys#slot ht 3)] ) + [test (##sys#slot ht 3)] + [rnd (##sys#slot ht 10)]) (let* ([hash (##sys#slot ht 4)] - [hshidx (hash key (##sys#size vec))] ) + [hshidx (hash key (##sys#size vec) rnd)] ) (if (eq? core-eq? test) ; Fast path (eq? is rewritten by the compiler): (let loop ([bucket (##sys#slot vec hshidx)]) @@ -848,9 +872,10 @@ (lambda (ht key) (##sys#check-structure ht 'hash-table 'hash-table-exists?) (let ([vec (##sys#slot ht 1)] - [test (##sys#slot ht 3)] ) + [test (##sys#slot ht 3)] + [rnd (##sys#slot ht 10)]) (let* ([hash (##sys#slot ht 4)] - [hshidx (hash key (##sys#size vec))] ) + [hshidx (hash key (##sys#size vec) rnd)] ) (if (eq? core-eq? test) ; Fast path (eq? is rewritten by the compiler): (let loop ([bucket (##sys#slot vec hshidx)]) @@ -874,7 +899,8 @@ (let* ([vec (##sys#slot ht 1)] [len (##sys#size vec)] [hash (##sys#slot ht 4)] - [hshidx (hash key len)] ) + [rnd (##sys#slot ht 10)] + [hshidx (hash key len rnd)] ) (let ([test (##sys#slot ht 3)] [newsiz (fx- (##sys#slot ht 2) 1)] [bucket0 (##sys#slot vec hshidx)] ) diff --git a/tests/hash-table-tests.scm b/tests/hash-table-tests.scm index 524c40d..6737c8f 100644 --- a/tests/hash-table-tests.scm +++ b/tests/hash-table-tests.scm @@ -38,7 +38,7 @@ (print "HT - All Parameters") (set! ht (make-hash-table eqv? eqv?-hash 23 #:test equal? #:hash equal?-hash - #:initial 'foo + #:initial 'foo #:randomization 30 #:size 500 #:min-load 0.45 #:max-load 0.85 #:weak-keys #t #:weak-values #t)) diff --git a/types.db b/types.db index 9f97c46..3ee83db 100644 --- a/types.db +++ b/types.db @@ -2448,11 +2448,11 @@ ;; srfi-69 (alist->hash-table (#(procedure #:clean #:enforce) alist->hash-table ((list-of pair) #!rest) (struct hash-table))) -(eq?-hash (#(procedure #:clean #:enforce) eq?-hash (* #!optional fixnum) fixnum)) -(equal?-hash (#(procedure #:clean #:enforce) equal?-hash (* #!optional fixnum) fixnum)) -(eqv?-hash (#(procedure #:clean #:enforce) eqv?-hash (* #!optional fixnum) fixnum)) -(hash (#(procedure #:pure #:enforce) hash (* #!optional fixnum) fixnum)) -(hash-by-identity (#(procedure #:pure #:enforce) hash-by-identity (* #!optional fixnum) fixnum)) +(eq?-hash (#(procedure #:clean #:enforce) eq?-hash (* #!optional fixnum fixnum) fixnum)) +(equal?-hash (#(procedure #:clean #:enforce) equal?-hash (* #!optional fixnum fixnum) fixnum)) +(eqv?-hash (#(procedure #:clean #:enforce) eqv?-hash (* #!optional fixnum fixnum) fixnum)) +(hash (#(procedure #:pure #:enforce) hash (* #!optional fixnum fixnum) fixnum)) +(hash-by-identity (#(procedure #:pure #:enforce) hash-by-identity (* #!optional fixnum fixnum) fixnum)) (hash-table->alist (#(procedure #:clean #:enforce) hash-table->alist ((struct hash-table)) (list-of pair))) (hash-table-clear! (#(procedure #:clean #:enforce) hash-table-clear! ((struct hash-table)) undefined)) (hash-table-copy (#(procedure #:clean #:enforce) hash-table-copy ((struct hash-table)) (struct hash-table))) @@ -2468,6 +2468,8 @@ (hash-table-hash-function (#(procedure #:clean #:enforce) hash-table-hash-function ((struct hash-table)) (procedure (* fixnum) fixnum)) (((struct hash-table)) (##sys#slot #(1) '4))) +(hash-table-randomization (#(procedure #:clean #:enforce) hash-table-randomization ((struct hash-table)) fixnum) + (((struct hash-table)) (##sys#slot #(1) '10))) (hash-table-initial (#(procedure #:clean #:enforce) hash-table-initial ((struct hash-table)) *)) (hash-table-keys (#(procedure #:clean #:enforce) hash-table-keys ((struct hash-table)) list)) (hash-table-map (#(procedure #:clean #:enforce) hash-table-map ((struct hash-table) (procedure (* *) *)) list)) @@ -2504,15 +2506,15 @@ ;;XXX if we want to hardcode hash-default-bound here, we could rewrite the 1-arg case... ; (applies to all hash-functions) -(keyword-hash (#(procedure #:clean #:enforce) keyword-hash (* #!optional fixnum) fixnum)) +(keyword-hash (#(procedure #:clean #:enforce) keyword-hash (* #!optional fixnum fixnum) fixnum)) (make-hash-table (#(procedure #:clean #:enforce) make-hash-table (#!rest) (struct hash-table))) -(number-hash (#(procedure #:clean #:enforce) number-hash (fixnum #!optional fixnum) fixnum)) -(object-uid-hash (#(procedure #:clean #:enforce) object-uid-hash (* #!optional fixnum) fixnum)) -(symbol-hash (#(procedure #:clean #:enforce) symbol-hash (symbol #!optional fixnum) fixnum)) -(string-hash (#(procedure #:clean #:enforce) string-hash (string #!optional fixnum fixnum fixnum) number)) -(string-hash-ci (#(procedure #:clean #:enforce) string-hash-ci (string #!optional fixnum fixnum fixnum) number)) -(string-ci-hash (#(procedure #:clean #:enforce) string-ci-hash (string #!optional fixnum fixnum fixnum) number)) +(number-hash (#(procedure #:clean #:enforce) number-hash (fixnum #!optional fixnum fixnum) fixnum)) +(object-uid-hash (#(procedure #:clean #:enforce) object-uid-hash (* #!optional fixnum fixnum) fixnum)) +(symbol-hash (#(procedure #:clean #:enforce) symbol-hash (symbol #!optional fixnum fixnum) fixnum)) +(string-hash (#(procedure #:clean #:enforce) string-hash (string #!optional fixnum fixnum fixnum fixnum) number)) +(string-hash-ci (#(procedure #:clean #:enforce) string-hash-ci (string #!optional fixnum fixnum fixnum fixnum) number)) +(string-ci-hash (#(procedure #:clean #:enforce) string-ci-hash (string #!optional fixnum fixnum fixnum fixnum) number)) ;; tcp