Stay in FD-STREAM-READ-SEQUENCE/UTF-8 after resync, replace restarts

These were returning NIL after those. This was no good for the control
flow in ANSI-STREAM-READ-STRING-FROM-FRC-BUFFER.
This commit is contained in:
Richard M Kreuter 2025-10-15 19:53:40 -04:00 committed by Stas Boukarev
parent a2c2464243
commit ca2b46c7c6
3 changed files with 120 additions and 62 deletions

View file

@ -919,72 +919,69 @@
(return index))))))
;;; Bypass the character buffer, the caller must ensure that it's empty
(macrolet ((def-fd-stream-read-sequence/utf-8 (name string-type simd-op newline-variant)
#-(and sb-unicode 64-bit little-endian)
(declare (ignore simd-op))
`(defun ,name (stream string start end
&aux (index start) ,@(when (eql newline-variant :crlf) '(eof)))
(declare (type fd-stream stream)
(type index start end index)
(type ,string-type string))
(block outer
(loop
(do ((instead (fd-stream-instead stream)))
((= (fill-pointer instead) 0)
(setf (fd-stream-listen stream) nil))
(setf (aref string index) (vector-pop instead))
(incf index)
(when (= index end)
(when (= (fill-pointer instead) 0)
(setf (fd-stream-listen stream) nil))
(return index)))
#+(and sb-unicode 64-bit little-endian)
(setf index (,simd-op index end string (fd-stream-ibuf stream)))
(let* ((ibuf (fd-stream-ibuf stream))
(head (buffer-head ibuf))
(tail (buffer-tail ibuf))
(sap (buffer-sap ibuf))
,@(when (eql newline-variant :crlf) '(requested-refill))
incomplete)
(declare (type index head tail))
(flet ((decode-break (reason)
(setf (buffer-head ibuf) head)
(when (stream-decoding-error-and-handle stream reason 1)
(return-from outer index))
(return)))
(utf8-char-loop :crlf ,(eql newline-variant :crlf)))
(when (= index end)
(setf (buffer-head ibuf) head)
(return index))
(unless (catch 'eof-input-catcher (refill-input-buffer stream))
(,@(if (eql newline-variant :crlf)
'(if requested-refill (setf eof t))
'(progn))
(when (or (not incomplete)
(stream-decoding-error-and-handle stream (- tail head) 1))
(return index))))))))))
(def-fd-stream-read-sequence/utf-8
fd-stream-read-sequence/utf-8-to-string
(macrolet ((def (name string-type simd-op newline-variant)
#-(and sb-unicode 64-bit little-endian)
(declare (ignore simd-op))
`(defun ,name (stream string start end
&aux (index start) ,@(when (eql newline-variant :crlf) '(eof)))
(declare (type fd-stream stream)
(type index start end index)
(type ,string-type string))
(tagbody
loop
(do ((instead (fd-stream-instead stream)))
((= (fill-pointer instead) 0)
(setf (fd-stream-listen stream) nil))
(setf (aref string index) (vector-pop instead))
(incf index)
(when (= index end)
(when (= (fill-pointer instead) 0)
(setf (fd-stream-listen stream) nil))
(return index)))
#+(and sb-unicode 64-bit little-endian)
(setf index (,simd-op index end string (fd-stream-ibuf stream)))
(let* ((ibuf (fd-stream-ibuf stream))
(head (buffer-head ibuf))
(tail (buffer-tail ibuf))
(sap (buffer-sap ibuf))
,@(when (eql newline-variant :crlf) '(requested-refill))
incomplete)
(declare (type index head tail))
(flet ((decode-break (reason)
(setf (buffer-head ibuf) head)
(if (stream-decoding-error-and-handle stream reason 1)
(return-from ,name index)
(go loop))))
(utf8-char-loop :crlf ,(eql newline-variant :crlf)))
(when (= index end)
(setf (buffer-head ibuf) head)
(return-from ,name index))
(unless (catch 'eof-input-catcher (refill-input-buffer stream))
(,@(if (eql newline-variant :crlf)
'(if requested-refill (setf eof t))
'(progn))
(when (or (not incomplete)
(stream-decoding-error-and-handle stream (- tail head) 1))
(return-from ,name index)))))
(go loop)))))
(def fd-stream-read-sequence/utf-8-to-string
(simple-array character (*))
sb-vm::simd-copy-utf8-to-character-string
:lf)
sb-vm::simd-copy-utf8-to-character-string
:lf)
#+sb-unicode
(def-fd-stream-read-sequence/utf-8
fd-stream-read-sequence/utf-8-to-base-string
simple-base-string
sb-vm::simd-copy-utf8-to-base-string
:lf)
(def-fd-stream-read-sequence/utf-8
fd-stream-read-sequence/utf-8-crlf-to-character-string
(def fd-stream-read-sequence/utf-8-to-base-string
simple-base-string
sb-vm::simd-copy-utf8-to-base-string
:lf)
(def fd-stream-read-sequence/utf-8-crlf-to-character-string
(simple-array character (*))
sb-vm::simd-copy-utf8-crlf-to-character-string
:crlf)
sb-vm::simd-copy-utf8-crlf-to-character-string
:crlf)
#+sb-unicode
(def-fd-stream-read-sequence/utf-8
fd-stream-read-sequence/utf-8-crlf-to-base-string
simple-base-string
sb-vm::simd-copy-utf8-crlf-to-base-string
:crlf))
(def fd-stream-read-sequence/utf-8-crlf-to-base-string
simple-base-string
sb-vm::simd-copy-utf8-crlf-to-base-string
:crlf))
#+(and sb-unicode 64-bit little-endian
(not (or arm64 x86-64)))

View file

@ -2345,12 +2345,16 @@ benefit of the function GET-OUTPUT-STREAM-STRING."
(replace seq %frc-buffer%
:start1 start
:start2 %frc-index%)
;; Isn't this DONE-WITH-FAST-READ-CHAR?
(setf (ansi-stream-in-index %frc-stream%)
+ansi-stream-in-buffer-length+)
(incf start buffered))))
(declare (inline refill-buffer copy))
(cond
;; Read directly into the string when possible
;; Caution: FD-STREAM-READ-SEQUENCE methods used to return
;; NIL sometimes. Now they just do all of READ-SEQUENCE's
;; contract. So don't worry about fall-thru here.
((and (> (- needed buffered)
(/ +ansi-stream-in-buffer-length+ 2))
(fd-stream-p stream)

View file

@ -364,6 +364,63 @@
(error "wanted ~S, got ~S (~S)" character got n))))
(assert (eql (read-char s nil s) s))))))
(with-test (:name (read-sequence :utf-8 :decoding-error-restarts))
(flet ((test (bytes replacement string-length element-type
expected-string expected-end &optional (start 0))
(let ((file *test-path*))
(with-open-file (stream file :direction :output
:if-exists :supersede
:element-type '(unsigned-byte 8))
(dolist (byte bytes) (write-byte byte stream)))
(unwind-protect
(with-open-file (stream file :external-format :utf-8
:element-type element-type)
(handler-bind
((sb-int:character-decoding-error
(lambda (ignore)
(declare (ignore ignore))
(typecase replacement
((or string character)
(invoke-restart 'sb-impl::input-replacement replacement))
(null (invoke-restart 'sb-int:attempt-resync))
((eql :eof) (invoke-restart 'sb-int:force-end-of-file))))))
(let* ((pad #\space)
(string (make-string string-length :initial-element pad)))
(let ((end (read-sequence string stream :start start)))
(assert
(equal (list (string-right-trim (list pad) string) end)
(list expected-string expected-end)))))))
(handler-case (delete-file file)
(file-error ()))))))
(let ((bad-bytes '(65 #xE0 66 67)))
;; This doesn't call FD-STREAM-READ-SEQUENCE/UTF-8-TO-STRING because
;; the requested number of characters, 10, is less than half a CIN
;; buffer size. So it gives the right answer.
(test bad-bytes #\? 10 'character "A?BC" 4)
;; A bivalent stream has no CIN buffer, so this won't call
;; FD-STREAM-READ-SEQUENCE/UTF-8-TO-STRING.
(test bad-bytes #\? 10 :default "A?BC" 4)
;; But when the stream has a CIN buffer and >256 characters are
;; requested, when FD-STREAM-READ-SEQUENCE/UTF-8-TO-STRING
;; encounters a decoding error, it returns NIL, which causes
;; ANSI-STREAM-READ-STRING-FROM-FRC-BUFFER to fall thru to code that
;; clobbers the string from the initial START arg.
(test bad-bytes #\? 257 'character "A?BC" 4) ;; comes out as "?BC", 3 in 2.5.9
;; One more for good luck
(test bad-bytes "xyz" 259 'character " AxyzBC" 8 2)
;; Now all 4 again but with ATTEMPT-RESYNC.
(test bad-bytes nil 10 'character "ABC" 3)
(test bad-bytes nil 10 :default "ABC" 3)
(test bad-bytes nil 257 'character "ABC" 3) ;; comes out as "BC", 2 in 2.5.9
(test bad-bytes nil 259 'character " ABC" 5 2) ;; comes out as "BC", 4 in 2.5.9
;; And again with FORCE-END-OF-FILE. (These all worked right in
;; 2.5.9, because after fall-thru there was nothing left in the
;; stream to overwrite the #\A.
(test bad-bytes :eof 10 'character "A" 1)
(test bad-bytes :eof 10 :default "A" 1)
(test bad-bytes :eof 257 'character "A" 1)
(test bad-bytes :eof 259 'character " A" 3 2))))
;;; Test character decode restarts.
(with-open-file (s *test-path* :direction :output
:if-exists :supersede :element-type '(unsigned-byte 8))