Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 9 additions & 4 deletions src/JSON.jl
Original file line number Diff line number Diff line change
Expand Up @@ -50,10 +50,15 @@ end
@noinline function _invalid(error, buf, pos::Int, typename::String)
# compute which line the error falls on by counting “\n” bytes up to pos
cus = buf isa AbstractString ? codeunits(buf) : buf
line_no = count(b -> b == UInt8('\n'), view(cus, 1:pos)) + 1

li = pos > 20 ? pos - 9 : 1
ri = min(sizeof(cus), pos + 20)
# `pos` can point one byte past the end: UnexpectedEOF is reported at the
# position we wanted to read, so every input ending mid-token lands here
# with pos == sizeof(cus) + 1. Clamp before slicing, or building the error
# message throws BoundsError instead of the ArgumentError we mean to raise.
n = sizeof(cus)
line_no = count(b -> b == UInt8('\n'), view(cus, 1:min(pos, n))) + 1

li = pos > 20 ? min(pos - 9, n) : 1
ri = min(n, pos + 20)
snippet_bytes = cus[li:ri]
snippet_pos = pos - li + 1
snippet = String(copy(snippet_bytes))
Expand Down
29 changes: 29 additions & 0 deletions test/parse.jl
Original file line number Diff line number Diff line change
Expand Up @@ -276,6 +276,35 @@ JSON.lift(::DateMaterializedObjectStyle, ::Type{Date}, x::JSON.Object) = Date(x[
@test x.url == "http://www.example.com/#\\\ud8000\\好"
end # @testset "errors"

@testset "truncated input reports UnexpectedEOF" begin
# UnexpectedEOF is raised at the byte we wanted to read, i.e. one past
# the end, so building the error message used to slice out of bounds and
# throw BoundsError from the error path itself.
for str in ("{", "[", "[1", "[1,", "\"a", "{\"a\"", "{\"a\":", "{\"a\":1,",
" ", "\n", "\t", "{\"a\":[1,2", "-")
@test_throws ArgumentError JSON.parse(str)
end
# Same for the typed and lazy entry points.
@test_throws ArgumentError JSON.parse("{\"a\":", @NamedTuple{a::Int})
@test_throws ArgumentError JSON.lazy("{\"a\":")[]
# ...and when the buffer is bytes rather than a string.
@test_throws ArgumentError JSON.parse(Vector{UInt8}("{\"a\":"))

# The message still carries a correct position, line number and snippet.
err = try
JSON.parse("{\n \"a\": 1,\n \"b\":")
nothing
catch e
e
end
@test err isa ArgumentError
msg = err.msg
@test occursin("UnexpectedEOF", msg)
@test occursin("byte position 18", msg) || occursin("byte position 19", msg)
@test occursin("line 3", msg)
@test occursin("<EOF>", msg)
end # @testset "truncated input reports UnexpectedEOF"

# JSON.jl pre-1.0 compat
x = JSON.parse("{}")
@test isempty(x) && typeof(x) == JSON.Object{String, Any}
Expand Down
Loading