Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit 582dbca

Browse files
committedMar 2, 2025·
Bugfix: Fix copyline for non-appending IOBuffer
1 parent 86dff75 commit 582dbca

File tree

2 files changed

+61
-23
lines changed

2 files changed

+61
-23
lines changed
 

‎base/iobuffer.jl

+52-23
Original file line numberDiff line numberDiff line change
@@ -7,19 +7,18 @@
77
# . are bytes which can neither be read nor written - but the IOBuffer has control
88
# of the full array.
99

10-
# uuuuuuuuuuuuuXXXXXXXXXXXXX------.......
11-
# | | | | | |
12-
# | | ptr size | |
13-
# 1 mark (zero-indexed) | lastindex(data)
14-
# maxsize
10+
# uuuuuuuuuuuuuXXXXXXXXXXXXX------------
11+
# | | | | | |
12+
# | | ptr size | maxsize (≥ lastindex)
13+
# 1 mark (zero-indexed) lastindex(data)
1514

1615
# AFTER COMPACTION
1716
# Mark, ptr and size decreases by `mark`
1817

19-
# uuuuuXXXXXXXXXXXXX--------------.......
20-
# | | | | |
21-
# | ptr size | lastindex(data)
22-
# mark (zero-indexed) maxsize
18+
# uuuuuXXXXXXXXXXXXX---------------------
19+
# || | | | |
20+
# |1 ptr size | maxsize (≥ lastindex)
21+
# mark (zero-indexed) lastindex(data)
2322

2423
# * The underlying array is always 1-indexed
2524
# * The IOBuffer has full control of the underlying array.
@@ -44,11 +43,12 @@ mutable struct GenericIOBuffer{T<:AbstractVector{UInt8}} <: IO
4443

4544
# Last index of `data` that has been written to. Data in size+1:end has not yet been used,
4645
# and may contain arbitrary values.
47-
# This value is always in 0 : min(maxsize, lastindex(data))
46+
# This value is always in 0 : lastindex(data)
4847
size::Int
4948

5049
# This is the maximum length that the buffer size can grow to.
51-
# This value is always in 0:typemax(Int)
50+
# This value is always in 0:typemax(Int).
51+
# We always have length(data) <= maxsize
5252
maxsize::Int
5353

5454
# Data is read/written from/to ptr, except in situations where append is true, in which case
@@ -62,6 +62,8 @@ mutable struct GenericIOBuffer{T<:AbstractVector{UInt8}} <: IO
6262
# This value is always == -1, or in 0:size-1
6363
mark::Int
6464

65+
# TODO: The invariants of the values should be enforced in all constructors,
66+
# except explicitly unsafe ones.
6567
function GenericIOBuffer{T}(data::T, readable::Bool, writable::Bool, seekable::Bool, append::Bool,
6668
maxsize::Integer) where T<:AbstractVector{UInt8}
6769
require_one_based_indexing(data)
@@ -163,6 +165,7 @@ function IOBuffer(
163165
truncate::Union{Bool,Nothing}=nothing,
164166
maxsize::Integer=typemax(Int),
165167
sizehint::Union{Integer,Nothing}=nothing)
168+
# TODO: Add a check that length(data) <= maxsize and error if not.
166169
if maxsize < 0
167170
throw(ArgumentError("negative maxsize"))
168171
end
@@ -684,6 +687,8 @@ end
684687
@inline function write(to::GenericIOBuffer, a::UInt8)
685688
ensureroom(to, UInt(1))
686689
ptr = (to.append ? to.size+1 : to.ptr)
690+
# We have just ensured there is room for 1 byte, EXCEPT if we were to exceed
691+
# maxsize. So, we just need to check that here.
687692
if ptr > to.maxsize
688693
return 0
689694
else
@@ -731,21 +736,45 @@ function copyuntil(out::IO, io::GenericIOBuffer, delim::UInt8; keep::Bool=false)
731736
end
732737

733738
function copyline(out::GenericIOBuffer, s::IO; keep::Bool=false)
734-
copyuntil(out, s, 0x0a, keep=true)
735-
line = out.data
736-
i = out.size # XXX: this is only correct for appended data. if the data was inserted, only ptr should change
737-
if keep || iszero(i) || line[i] != 0x0a
739+
# If the data is copied into the middle of the buffer of `out` instead of appended to the end,
740+
# and !keep, and the line copied ends with \r\n, then the copyuntil (even if keep=false)
741+
# will overwrite one too many bytes with the new \r byte.
742+
# Work around this by making a new temporary buffer.
743+
# Could perhaps be done better
744+
if !out.append && out.ptr < out.size + 1
745+
newbuf = IOBuffer()
746+
copyuntil(newbuf, s, 0x0a, keep=true)
747+
v = take!(newbuf)
748+
# Remove \r\n or \n if present
749+
if !keep
750+
if length(v) > 1 && last(v) == UInt8('\n')
751+
pop!(v)
752+
end
753+
if length(v) > 1 && last(v) == UInt8('\r')
754+
pop!(v)
755+
end
756+
end
757+
write(out, v)
738758
return out
739-
elseif i < 2 || line[i-1] != 0x0d
740-
i -= 1
741759
else
742-
i -= 2
743-
end
744-
out.size = i
745-
if !out.append
746-
out.ptr = i+1
760+
# Else, we can just copy the data directly into the buffer, and then
761+
# subtract the last one or two bytes depending on `keep`.
762+
copyuntil(out, s, 0x0a, keep=true)
763+
line = out.data
764+
i = out.size
765+
if keep || iszero(i) || line[i] != 0x0a
766+
return out
767+
elseif i < 2 || line[i-1] != 0x0d
768+
i -= 1
769+
else
770+
i -= 2
771+
end
772+
out.size = i
773+
if !out.append
774+
out.ptr = i+1
775+
end
776+
return out
747777
end
748-
return out
749778
end
750779

751780
function _copyline(out::IO, io::GenericIOBuffer; keep::Bool=false)

‎test/iobuffer.jl

+9
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,15 @@ end
128128
@test take!(copy(b)) == b"abcdeabcabc\n"
129129
copyline(b, a; keep=false)
130130
@test take!(copy(b)) == b"abcdeabcabc\nac"
131+
132+
# Test a current bug in copyline
133+
a = Base.SecretBuffer("abcde\r\n")
134+
b = IOBuffer()
135+
write(b, "xxxxxxxxxx")
136+
seek(b, 2)
137+
copyline(b, a; keep=false)
138+
Base.shred!(a)
139+
@test take!(b) == b"xxabcdexxx"
131140
end
132141

133142
@testset "take!" begin

0 commit comments

Comments
 (0)
Please sign in to comment.