Skip to content

Commit 80daf4c

Browse files
authored
Fix BigFloat MPFR parsing overread (#200)
* fix(bigfloat): provide nul-terminated mpfr input Preserve the existing zero-copy path for sources that are already safely NUL-terminated, while manufacturing a NUL-terminated candidate buffer only when needed before calling mpfr_strtofr.\n\nRefs #189 and #193. * perf(bigfloat): avoid copies for byte vector views Let contiguous writable byte views use the same temporary-NUL strategy as Vector{UInt8}, while keeping non-contiguous views on the safe copied-buffer path.\n\nThe fast path now distinguishes pointer-contiguous sources from writable-contiguous sources so CodeUnits remains immutable-safe.
1 parent 6352c4b commit 80daf4c

2 files changed

Lines changed: 123 additions & 19 deletions

File tree

src/floats.jl

Lines changed: 92 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -40,30 +40,103 @@ function typeparser(conf::AbstractConf{T}, source, pos, len, b, code, pl, option
4040
return pos, code, pl, T(x)
4141
end
4242

43+
function _copy_nulterminated(source::AbstractVector{UInt8}, pos, len)
44+
vlen = max(0, len)
45+
buf = Vector{UInt8}(undef, vlen + 1)
46+
vlen > 0 && copyto!(buf, 1, source, pos, vlen)
47+
@inbounds buf[vlen + 1] = 0x00
48+
return buf
49+
end
50+
51+
@inline _contiguous(source) = false
52+
@inline _contiguous(source::StridedVector{UInt8}) = stride(source, 1) == 1
53+
@inline _writable_contiguous(source) = false
54+
@inline _writable_contiguous(source::Vector{UInt8}) = true
55+
@inline _writable_contiguous(source::SubArray{UInt8,1}) =
56+
parent(source) isa Vector{UInt8} && _contiguous(source)
57+
58+
@inline _nulterminated(source, len) = false
59+
@inline _nulterminated(source::String, len) = len == sizeof(source)
60+
@inline _nulterminated(source::Base.CodeUnits{UInt8,String}, len) = len == length(source)
61+
@inline function _nulterminated(source::AbstractVector{UInt8}, len)
62+
return _contiguous(source) && len < length(source) && @inbounds(source[len + 1]) == 0x00
63+
end
64+
65+
@inline function _ends_on_delimiter(source::AbstractVector{UInt8}, len, options)
66+
0 < len <= length(source) || return false
67+
@inbounds b = source[len]
68+
return b == UInt8('\n') || b == UInt8('\r') || (options.flags.checkdelim && options.delim == b)
69+
end
70+
71+
function _bigfloat_buffer(source::AbstractVector{UInt8}, pos, len, b, code, options)
72+
_, _, pl, _ = typeparser(DefaultConf{String}(), source, pos, len, b, code, poslen(pos, 0), options)
73+
return _copy_nulterminated(source, pl.pos, pl.len)
74+
end
75+
76+
_bigfloat_buffer(source::AbstractString, pos, len, b, code, options) =
77+
_bigfloat_buffer(codeunits(source), pos, len, b, code, options)
78+
79+
function _bigfloat_buffer(source::IO, pos, len, b, code, options)
80+
_, _, pl, _ = typeparser(DefaultConf{String}(), source, pos, len, b, code, poslen(pos, 0), options)
81+
vlen = max(0, pl.len)
82+
buf = Vector{UInt8}(undef, vlen + 1)
83+
fastseek!(source, pl.pos - 1)
84+
readbytes!(source, buf, vlen)
85+
@inbounds buf[vlen + 1] = 0x00
86+
fastseek!(source, pos - 1)
87+
return buf
88+
end
89+
90+
@inline function _mpfr_strtofr(z, ptr, endptr, base, rounding)
91+
return ccall((:mpfr_strtofr, :libmpfr), Int32, (Ref{BigFloat}, Cstring, Ref{Ptr{UInt8}}, Int32, Base.MPFR.MPFRRoundingMode), z, ptr, endptr, base, rounding)
92+
end
93+
94+
@inline function _finish_bigfloat(source, pos, code, pl, z, ptr, base, rounding)
95+
endptr = Ref{Ptr{UInt8}}()
96+
err = _mpfr_strtofr(z, ptr, endptr, base, rounding)
97+
code |= endptr[] == ptr ? INVALID : OK
98+
pos += Int(endptr[] - ptr)
99+
source isa IO && fastseek!(source, pos - 1)
100+
return pos, code, PosLen(pl.pos, max(0, pos - pl.pos)), z
101+
end
102+
43103
function typeparser(::AbstractConf{BigFloat}, source, pos, len, b, code, pl, options)
44104
base = 0
45105
rounding = Base.MPFR.ROUNDING_MODE[]
46106
z = BigFloat(precision=Base.MPFR.DEFAULT_PRECISION[])
47-
if source isa AbstractVector{UInt8} || source isa String
48-
str = source
49-
strpos = pos
107+
if _nulterminated(source, len)
108+
GC.@preserve source begin
109+
return _finish_bigfloat(source, pos, code, pl, z, pointer(source, pos), base, rounding)
110+
end
111+
elseif _writable_contiguous(source)
112+
nulpos = if _ends_on_delimiter(source, len, options)
113+
len
114+
else
115+
_, _, strpl, _ = typeparser(DefaultConf{String}(), source, pos, len, b, code, poslen(pos, 0), options)
116+
strpl.pos + strpl.len
117+
end
118+
if nulpos <= length(source)
119+
@inbounds byte = source[nulpos]
120+
@inbounds source[nulpos] = 0x00
121+
try
122+
GC.@preserve source begin
123+
return _finish_bigfloat(source, pos, code, pl, z, pointer(source, pos), base, rounding)
124+
end
125+
finally
126+
@inbounds source[nulpos] = byte
127+
end
128+
else
129+
_, _, strpl, _ = typeparser(DefaultConf{String}(), source, pos, len, b, code, poslen(pos, 0), options)
130+
buf = _copy_nulterminated(source, strpl.pos, strpl.len)
131+
GC.@preserve buf begin
132+
return _finish_bigfloat(source, pos, code, pl, z, pointer(buf), base, rounding)
133+
end
134+
end
50135
else
51-
_, _, _pl, _ = typeparser(String, source, pos, len, b, code, pl, options)
52-
_pos = position(source)
53-
vpos, vlen = _pl.pos, _pl.len
54-
fastseek!(source, vpos - 1)
55-
str = Base.StringVector(vlen)
56-
strpos = 1
57-
readbytes!(source, str, vlen)
58-
fastseek!(source, _pos) # reset IO to earlier position
59-
end
60-
GC.@preserve str begin
61-
ptr = pointer(str, strpos)
62-
endptr = Ref{Ptr{UInt8}}()
63-
err = ccall((:mpfr_strtofr, :libmpfr), Int32, (Ref{BigFloat}, Cstring, Ref{Ptr{UInt8}}, Int32, Base.MPFR.MPFRRoundingMode), z, ptr, endptr, base, rounding)
64-
code |= endptr[] == ptr ? INVALID : OK
65-
pos += Int(endptr[] - ptr)
66-
return pos, code, PosLen(pl.pos, max(0, pos - pl.pos)), z
136+
buf = _bigfloat_buffer(source, pos, len, b, code, options)
137+
GC.@preserve buf begin
138+
return _finish_bigfloat(source, pos, code, pl, z, pointer(buf), base, rounding)
139+
end
67140
end
68141
end
69142

test/floats.jl

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -480,6 +480,37 @@ end
480480
end
481481

482482
@testset "BigFloats" begin
483+
res = Parsers.xparse(BigFloat, Vector(codeunits("1")), 1, 1)
484+
@test res.val == BigFloat(1)
485+
@test res.code == OK
486+
@test res.tlen == 1
487+
488+
@test Parsers.parse(BigFloat, Vector(codeunits("12")), Parsers.Options(), 1, 1) == BigFloat(1)
489+
@test Parsers.parse(BigFloat, codeunits("12"), Parsers.Options(), 1, 1) == BigFloat(1)
490+
491+
bytes = Vector(codeunits("xx123,yy"))
492+
res = Parsers.xparse(BigFloat, @view(bytes[3:6]), 1, 4)
493+
@test res.val == BigFloat(123)
494+
@test res.code == (OK | DELIMITED)
495+
@test res.tlen == 4
496+
@test String(bytes) == "xx123,yy"
497+
498+
bytes = Vector(codeunits("1x2"))
499+
res = Parsers.xparse(BigFloat, @view(bytes[1:2:3]), 1, 2)
500+
@test res.val == BigFloat(12)
501+
@test res.code == OK
502+
@test res.tlen == 2
503+
504+
res = Parsers.xparse(BigFloat, IOBuffer("1,"), 1, 2)
505+
@test res.val == BigFloat(1)
506+
@test res.code == (OK | DELIMITED)
507+
@test res.tlen == 2
508+
509+
res = Parsers.xparse(BigFloat, IOBuffer("1a,"), 1, 3)
510+
@test res.val == BigFloat(1)
511+
@test res.code == (OK | DELIMITED | INVALID_DELIMITER)
512+
@test res.tlen == 3
513+
483514
@test Parsers.parse(BigFloat, "1.7976931348623157e308") == Base.parse(BigFloat, "1.7976931348623157e308")
484515
@test Parsers.parse(BigFloat, "-1.7976931348623157e308") == Base.parse(BigFloat, "-1.7976931348623157e308")
485516
# next float64 - too large

0 commit comments

Comments
 (0)