Fix string-replace when the string is mutable

The `from` string argument is converted to a regexp and cached. When `from` is
a mutable string this can cause wrong results in the following calls
to string-replace. So the string is first converted to an immutable string to
be used as the key for the cache.
This commit is contained in:
Gustavo Massaccesi 2015-08-14 00:09:53 -03:00
parent 80aac79507
commit 1753335d34
2 changed files with 31 additions and 5 deletions

View File

@ -483,4 +483,11 @@
(test "foo\\1bar" string-replace "foo===bar" #rx"(=+)" "\\1")
(test "foo\\1bar" string-replace "foo===bar" #px"={3}" "\\1"))
;test that mutable string are not cached incorrectly
(let ([str (string-copy "_1_")])
(test "!!! _2_" string-replace "_1_ _2_" str "!!!") ;add str to the internal cache
(string-set! str 1 #\2)
(test "_1_ !!!" string-replace "_1_ _2_" str "!!!") ;verify that the new str is used
)
(report-errs)

View File

@ -100,17 +100,36 @@
(string-join (internal-split 'string-normalize-spaces str sep trim? +?)
space))
(define replace-cache (make-weak-hasheq))
;; Caches for string-replace:
;; A mutable string weakly holds a immutable copy until it is collected
;; or modified (and used as a argument of string-replace).
;; The immutable copy weakly holds the regexp that is used in string-replace.
;; Using string->immutable-string directly in string-replace is not a useful
;; because the immutable copy could be immediately collected.
(define immutable-cache (make-weak-hasheq))
(define (string->immutable-string/cache str)
(if (immutable? str)
str
(let ([old (hash-ref immutable-cache str #f)])
(if (and old (string=? str old))
old
(let ([new (string->immutable-string str)])
(hash-set! immutable-cache str new)
new)))))
(define replace-cache (make-weak-hash))
(define (string-replace str from to #:all? [all? #t])
(unless (string? str) (raise-argument-error 'string-replace "string?" str))
(unless (string? to) (raise-argument-error 'string-replace "string?" to))
(unless (or (string? from) (regexp? from))
(raise-argument-error 'string-replace "(or/c string? regexp?)" from))
(define from*
(if (regexp? from)
from
(hash-ref! replace-cache from
(λ() (if (string? from)
(regexp (regexp-quote from))
(raise-argument-error 'string-replace "string?" from))))))
(hash-ref! replace-cache (string->immutable-string/cache from)
(λ() (regexp (regexp-quote from))))))
(define to* (regexp-replace-quote to))
(if all?
(regexp-replace* from* str to*)