332 Commits
Author SHA1 Message Date
Jean Boussier 86f192cf38 [ruby/json] parser.c: only update the stack once fully spilled
https://github.com/ruby/json/commit/78d6a83a3c
2026-10-12 14:28:23 +00:00
Jean Boussier c5597ac4bd [ruby/json] Fix parsing of number with out of range negative exponents
https://github.com/ruby/json/commit/6ea62016fb
2026-10-12 14:22:32 +00:00
Jean Boussier 817023b888 [ruby/json] Fix parsing of unescaped NULL bytes in strings
https://github.com/ruby/json/commit/1b67eb3db2
2026-10-12 13:57:23 +00:00
Jean Boussier 90e7ab3ade [ruby/json] ResumableParser: fix handling of incomplete unicode escape
`spe` is the string end, so if the escape is incomplete, it doesn't
mean we reached the end of stream, hence it's a syntax error, not
an EOS error.

https://github.com/ruby/json/commit/c1a4372837
2026-10-12 13:51:08 +00:00
Jean Boussier 0e01c2a87f [ruby/json] More hardening against adversarial encode methods
https://github.com/ruby/json/commit/429c109458
2026-10-12 08:15:47 +00:00
Hiroshi SHIBATAandClaude Opus 5.5 87e6f05163 Use rb_len_t for String lengths and positions in json
The column of a parse error is a character position in the source, so it follows them while the line number stays long. It falls back to long on Ruby versions that do not define HAVE_RB_LEN_T.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 08:10:53 +09:00
ydah 8cf3b6691f [ruby/json] Fix parser error positions for newlines and multibyte characters
https://github.com/ruby/json/commit/590dd5448d
2026-10-02 13:03:45 +00:00
ydah a349827f06 [ruby/json] Preserve complete UTF-8 characters in parse error snippets
https://github.com/ruby/json/commit/c484f8fa82
2026-10-02 12:21:27 +00:00
ydah f452e5403e [ruby/json] Prevent ResumableParser from stalling on invalid object keys
https://github.com/ruby/json/commit/452f31b569
2026-10-01 06:21:36 +00:00
ydah 4f9ec01a51 [ruby/json] Prevent crashes with non-Proc on_load callbacks
Convert non-Proc on_load callbacks through to_proc and validate the result in the shared parser configuration. Previously, arbitrary values were stored and passed directly to rb_proc_call_with_block.

Cover Method callbacks in JSON.parse, JSON::Coder, and ResumableParser, along with invalid types, invalid conversion results, and nil or false callbacks.

https://github.com/ruby/json/commit/672da19fd5
2026-09-30 15:42:28 +00:00
Elankumaran Srinivasan 4219a1aa68 [ruby/json] Fix parser silently accepting a lone trailing surrogate in string values
The C parser guards leading-first surrogate pairs but silently accepts
a lone trailing surrogate, encoding U+DC00..U+DFFF as three UTF-8 bytes
into the output buffer. The returned Ruby String is tagged UTF-8 but
fails valid_encoding? and raises misleading errors from downstream
String operations (upcase, split, regex, encode, JSON.generate).

Add a symmetric branch alongside the leading-surrogate check to raise
JSON::ParserError at the parse boundary. Extend test_invalid_surogates
with the new cases and move the three JSONTestSuite fixtures that this
fix newly rejects from INVALID_ENCODING_TESTS into UNDEFINED_FAILING,
closing the CRuby/JRuby parser parity gap documented in the file.

https://github.com/ruby/json/commit/82bf6bedcf
2026-09-07 06:53:10 +00:00
Ojus ChughandJean Boussier 065a5592d2 [ruby/json] Add JSON::ParserError#json_path
(https://github.com/ruby/json/pull/1062)

Add JSON::ParserError#json_path

https://github.com/ruby/json/commit/9ae76bdd3d

Co-authored-by: Jean Boussier <byroot@ruby-lang.org>
2026-08-24 15:04:10 +00:00
Jean Boussier ac60d4ae4d [ruby/json] Make generator options strict
https://github.com/ruby/json/commit/a8d7f6da6b
2026-08-11 08:12:51 +00:00
Jean Boussier e79dd68040 [ruby/json] Make parsing options keyword arguments
Or at least behave like them.

https://github.com/ruby/json/commit/ebf0f2901c
2026-08-11 08:12:50 +00:00
Jean Boussier 8bf14d3a70 [ruby/json] Make allow_comments: false the default
https://github.com/ruby/json/commit/bc24969dab
2026-08-11 08:12:49 +00:00
Jean Boussier 9f836d7d13 [ruby/json] Make allow_duplicate_key: false the default
https://github.com/ruby/json/commit/e7636af6e6
2026-08-11 08:12:49 +00:00
Scott Myron 7e1a7201c4 [ruby/json] Widen the json_decode_integer fast path to more 64-bit integers.
When this code was refactored in 9c4db31, the mantissa_digits captured
only the length of the mantissa and the negative sign was captured
separately. Additionally, the paths diverged between signed and
unsigned values. However, MAX_FAST_INTEGER_SIZE stayed at 18 which limited
the range of uint64_t and int64_t that took the fast path.

This change expands the range of uint64_t and int64_t's that can be
returned by the fast paths without falling back to
json_decode_large_number.

https://github.com/ruby/json/commit/0238abcaf3
2026-08-07 07:37:27 +00:00
Jean Boussier 8a44311680 [ruby/json] Also don't compute ResumableParser cursor position for warning
Same reason we don't for errors, since the resumable parser doesn't
keep the entire stream in memory, it can't always be accurate, and
the buffer may even have been freed.

Anyway, for a long lived stream, it wouldn't be very helpful either.

Also adds some assertions to ensure the buffer liveness.

https://github.com/ruby/json/commit/2c332bfe2b
2026-07-31 06:22:00 +00:00
Nobuyoshi Nakada b67f6129e3 [Misc #22206] Add dependency updater to mkmf
Allow extension projects to update marked dependency sections after
`create_makefile` has resolved their source and object lists.

Share the dependency scanner with `tool/mkdepend.rb` so bundled and
upstream extensions can generate the same dependency files.
2026-07-24 17:45:58 +09:00
Nobuyoshi Nakada 7d4461e162 [Misc #22206] Generate dependencies without compiler builds
Replace `tool/update-deps` with a Ruby source scanner so dependency
updates no longer require GNU make, a configured build, or compiler
preprocessor output.

Keep compact source mappings and scanner declarations in dependency
files, and expand complete rules into the build directory.  Avoid the
temporary fixture headers required by the compiler-driven prototype.

Use the generated rules from GNU make, BSD make, and NMake builds.
Support out-of-tree and read-only source trees, and retain expanded
dependencies shipped in release archives when baseruby is unavailable.
2026-07-23 00:04:47 +09:00
Scott Myron c8b1a0f6c5 [ruby/json] Pass '-mno-outline' to the compiler (if supported) to prevent function outlining.
https://github.com/ruby/json/commit/f5fc2454fd
2026-07-22 06:53:01 +00:00
Jean Boussier 5a6cbaa291 [ruby/json] Rely on Ruby's HAVE_BUILTIN___BUILTIN_CLZLL
We don't need to perform out own checks.

Fix: https://github.com/ruby/json/pull/1052

https://github.com/ruby/json/commit/db70b14321
2026-07-13 07:26:32 +00:00
Masataka Pocke KuwabaraandClaude Opus 4.8 d65b0ec0ef [ruby/json] Avoid re-decoding an incomplete number on every ResumableParser chunk
`JSON::ResumableParser` re-decodes an incomplete number on every chunk it
receives, so a long number delivered in small chunks is disproportionately slow
to parse -- the work is quadratic (a bit worse in practice) in the number's
length.

## Background

When the parser reads a number that reaches the end of the buffer, it cannot
tell whether more digits will arrive in a later chunk, so it rewinds the cursor
and returns `false` to wait for more input. Until now it fully decoded the
number before that check -- and then discarded the result. For a long run of
digits that decode builds a bignum, whose cost grows faster than linearly with
the digit count, and it was repeated on every chunk that extended the number.

The total cost is therefore quadratic (a bit worse in practice) in the length
of the number. The same "rewind and re-process" happens for incomplete strings
and comments, but those only re-scan (no value construction), so only numbers
pay this decode cost.

## Benchmark

Feeding an incomplete token in 128-byte chunks, one iteration being one full
chunked feed (`benchmark-ips`; macOS 15.6 arm64; ruby 4.0.0):

```ruby
require "json"
require "benchmark/ips"

def feed_incomplete(token, chunk_size)
  parser = JSON::ResumableParser.new
  i = 0
  while i < token.bytesize
    parser << token.byteslice(i, chunk_size)
    i += chunk_size
    parser.parse
  end
end

CHUNK = 128
sizes = [100, 16_000, 32_000, 64_000, 128_000]

Benchmark.ips do |x|
  x.config(time: 3, warmup: 1)
  sizes.each do |n|
    number = "1" * n
    string = '"' + ("a" * n)
    x.report("number #{n} digits") { feed_incomplete(number, CHUNK) }
    x.report("string #{n} chars")  { feed_incomplete(string, CHUNK) }
  end
end
```

Per-iteration time, before vs. after this change:

| Input (128-byte chunks) | Before   | After    | Change          |
|-------------------------|---------:|---------:|-----------------|
| number, 100 digits      | 949 ns   | 414 ns   | negligible      |
| number, 16,000 digits   | 16.4 ms  | 0.138 ms | ~119x faster    |
| number, 32,000 digits   | 94.8 ms  | 0.531 ms | ~178x faster    |
| number, 64,000 digits   | 540.8 ms | 2.05 ms  | ~264x faster    |
| number, 128,000 digits  | 3.07 s   | 8.13 ms  | ~378x faster    |
| string, 100 chars       | 411 ns   | 391 ns   | ~1x (unchanged) |
| string, 16,000 chars    | 49.4 μs  | 47.7 μs  | ~1x (unchanged) |
| string, 32,000 chars    | 169.7 μs | 165.8 μs | ~1x (unchanged) |
| string, 64,000 chars    | 621 μs   | 616 μs   | ~1x (unchanged) |
| string, 128,000 chars   | 2.38 ms  | 2.35 ms  | ~1x (unchanged) |

The string rows are unchanged, confirming the change is number-specific.

## Fix

Defer the decode. `json_parse_number` still scans the digits (cheap, and enough
to advance the cursor so the caller can detect an incomplete number), but when
the scan reaches the end of the buffer in resumable mode it returns `Qundef`
without decoding. The caller already rewinds and returns `false` in exactly that
case, so the decoded value was never used there. The number is decoded once,
when a following byte proves it complete.

Non-resumable `JSON.parse` is unaffected: it passes `resumable = false`, so the
early return is never taken and the number is decoded as before. A test checks
that large integers and floats split across feeds still decode to the correct
value.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PpeQSEFkF1X1Uuzjx9BsYe

https://github.com/ruby/json/commit/d1299d42ac
2026-07-09 09:43:03 +00:00
Masataka Pocke KuwabaraandClaude Opus 4.8 f3f3270f70 [ruby/json] Fix ResumableParser losing tokens before a feed-boundary suspension
When a feed boundary fell right after a consumed token -- a `,` or an opening
`[` / `{` -- whose effect was not yet committed to the parser's persistent
state, the token was dropped on resume, producing parse errors or silently
wrong values.

## Reproduction

```ruby
require "json"

def parse_in_two(first, second, **opts)
  parser = JSON::ResumableParser.new(**opts)
  parser << first
  parser.parse
  parser << second
  parser.parse
  parser.value
rescue JSON::ParserError => e
  e.message
end

# a ',' and its closing bracket split across the boundary
p parse_in_two("[1,", "]", allow_trailing_comma: true)
# a comment right after '[' split across the boundary
p parse_in_two("[/*", "*/1]", allow_comments: true)
```

## Expected Behavior

```
[1]
[1]
```

Feeding `[1,]` or `[/**/1]` in a single chunk parses to `[1]`, so splitting it
at the comma or inside the comment should make no difference.

## Actual Behavior

```
"unexpected character: ']'"
1
```

For the comma, the closing bracket that arrived in the later chunk was rejected
because the comma had been forgotten. For the bracket, the array's first
element leaked out as a bare top-level document (`1` instead of `[1]`), and a
following `parse` would raise `unexpected character: ']'` on the orphaned
closing bracket. The same shapes affect objects (`{"a":1,` + `}`, `{/*` +
`*/"a":1}`), line comments (`[//` + `"x\n1]"`), and comments right after a
comma. It happens with the default configuration too, since comments are
accepted (with a deprecation warning) unless disabled.

## Description

Both bugs share one root cause: the parser consumed a token, then hit a
suspension point (end of buffer, possibly inside a comment) before recording
the token's effect in the persistent frame/phase state, so resuming had no
memory of the token. They differ in where that record lives, so the fix has
two parts.

On `,`, the comma phases used to eat whitespace and comments before
committing the frame's phase, in order to peek for a trailing-comma close.
The fix advances the phase to `JSON_PHASE_VALUE` (array) or
`JSON_PHASE_OBJECT_KEY` (object) immediately after consuming the comma.
Trailing-comma detection moves into those phases: an element/key position
that finds `]` / `}` (only reachable after a comma, since an empty container
closes inline) hands off to the comma phase to close.

On `[` / `{`, the bracket's frame is only pushed after the empty-container
check, and pushing it earlier is not an option: an empty container is decoded
inline without a frame, which is what keeps `[]` from consuming a nesting
level (`[[]]` parses with `max_nesting: 1`). Committing a frame first would
break that property or require reworking nesting accounting, affecting the
non-resumable parser as well. Instead, widen the rewind: the suspension
already rewinds the cursor, it just rewound to the start of the comment,
which is after the bracket. `json_eat_comments` gains a `resume_pos` that,
in resumable mode only, overrides the rewind target for all three suspension
sites (unterminated block comment, unterminated line comment, and a comment
marker split at the boundary), and the `[` / `{` cases pass the bracket's own
position via the new `json_eat_whitespace_resume_at` variant. Re-reading the
bracket and the comment on resume mirrors how strings and numbers already
rewind to `value_start` when they hit the end of the buffer. Non-resumable
parsing is unaffected in both parts: the same tokens are consumed in the same
order, and error positions still point at the comment.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PpeQSEFkF1X1Uuzjx9BsYe

https://github.com/ruby/json/commit/cc010c4688
2026-07-09 05:57:30 +00:00
Shizuo FujitaandClaude Fable 5 cf42ce9d17 [ruby/json] Add JSON::ResumableParser#partial_value? and #empty?
There was no single API answering "does the stream end in the middle of
a document?" once all parseable fed bytes have been consumed. Callers
had to combine two complementary APIs:

  !parser.rest.empty? || !parser.partial_value.nil?

`rest` only reflects unconsumed tokenizer bytes, so it is empty when the
stream is truncated exactly on a token boundary (right after a ':' or
','), while `partial_value` is nil when truncation happens mid-token
before any container is registered. Neither alone covers all shapes,
and `partial_value` materializes the partially built Ruby objects just
to test for nil.

`partial_value?` answers the same question as `!partial_value.nil?` by
looking at the parser's internal value stack directly, without building
the partial Ruby object graph.

`empty?` is strict: true only when the buffer is fully consumed, no
document is under construction and no parsed value awaits retrieval
with `value`. It is defined in Ruby as the composition of the three
underlying predicates so its definition doubles as documentation:

  def empty?
    eos? && !partial_value? && !value?
  end

https://github.com/ruby/json/commit/0864e83701

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-03 09:24:56 +00:00
Masataka Pocke KuwabaraandClaude Opus 4.8 31bf04b409 [ruby/json] Fix ResumableParser treating unterminated line comments as complete
A `//` line comment split across a feed boundary was treated as fully
consumed, so comment body delivered in a later chunk leaked out as parsed
JSON values.

## Reproduction

```ruby
require "json"

parser = JSON::ResumableParser.new(allow_comments: true)
documents = []
"[1]//[999]\n[3]".each_char do |char|
  parser << char
  documents << parser.value while parser.parse
end
p documents
```

## Expected Behavior

```
[[1], [3]]
```

The `[999]` is inside the `//` comment (which runs until the newline), so it
is ignored, leaving the two documents `[1]` and `[3]`. This is what feeding
the whole string in a single chunk produces.

## Actual Behavior

```
[[1], [999], [3]]
```

The commented-out `[999]` leaked out as a real value. When the comment body
does not form valid JSON the split feed raises a ParserError instead. This
happens with the default configuration too, since comments are accepted
(with a deprecation warning) unless disabled.

## Description

A `//` line comment is only terminated by a newline. `json_eat_comments`
searched for that newline and, when none was present in the buffer, moved
the cursor to the end of the buffer and returned as if the comment had been
fully consumed.

For the one-shot `JSON.parse` this is correct: the buffer holds the whole
document, so a `//` comment with no trailing newline genuinely runs to the
end of input. For `ResumableParser`, however, the end of the buffer is only
a chunk boundary, not the end of input. Reaching it does not mean the comment
is over -- the newline (and possibly more comment body) may still arrive in a
later `<<`. Swallowing the buffered bytes as a finished comment loses the
"still inside a comment" state, so any comment body delivered in the next
chunk was parsed as JSON instead of being ignored. Because the result then
depended on where the input happened to be split, the same byte stream could
produce different values.

The block-comment branch already handles this correctly: when it cannot find
the closing `*/` it rewinds to the comment start and raises an EOS-tagged
error, which `ResumableParser#parse` swallows into a `false` return so the
comment is retried once more input is available. This change makes the
line-comment branch behave the same way, but only in resumable mode
(detected via `state->parser`, the same discriminator `raise_parse_error`
already uses). In non-resumable mode the previous behaviour is preserved: a
`//` comment with no trailing newline is still consumed to the end of input.

As a consequence, a stream that ends with an unterminated line comment now
stays incomplete (`parse` keeps returning `false`) until a newline arrives,
which mirrors how a trailing bare number is only considered complete once a
following separator is seen.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PpeQSEFkF1X1Uuzjx9BsYe

https://github.com/ruby/json/commit/9dbfeb83df
2026-07-03 09:22:42 +00:00
tompngandClaude Opus 4.8 e5388655c7 [ruby/json] Fix ResumableParser#partial_value SEGV on incomplete nested containers
partial_value rebuilds the open containers on a scratch copy of the value
stack. Folding an empty innermost container pops nothing and pushes its
result, so head climbs one past the live size; the scratch buffer was sized
to exactly the live size, so the push triggered rvalue_stack_grow which
reallocated the ALLOCV buffer (a non-malloc pointer) and crashed.

A parent fold always reclaims its child's slot, so head exceeds the live
size by at most one -- either for the missing-value placeholder or for an
empty innermost fold, never both. Size the buffer to capa + 1.

https://github.com/ruby/json/commit/94c1af25b5

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-30 06:59:27 +00:00
copilot-swe-agent[bot] 0db07b1a5a [ruby/json] Avoid Float out of range warning for clearly underflowing numbers
When parsing JSON floats with extremely negative exponents (like
123.456e-789 or 123e-10000000), the parser would fall back to
rb_cstr_to_dbl which internally calls strtod. When strtod returns
ERANGE due to underflow to 0.0, Ruby emits a "Float out of range"
warning, causing noise in the test output.

Fix: when mantissa_digits + exponent < -324, the effective value is
less than 10^(-324) < DBL_TRUE_MIN/2, so it must round to 0.0 in
IEEE 754 round-to-nearest. Return 0.0 directly without going through
rb_cstr_to_dbl, avoiding the spurious warning.

This fixes warnings introduced by JSONMinefieldParserTest tests
(test_i_number_double_huge_neg_exp and test_i_number_real_underflow)
added in commit https://github.com/ruby/json/commit/6507a836c5.

https://github.com/ruby/json/commit/724eddaeaa
2026-06-27 06:16:26 +00:00
Nobuyoshi Nakada 061b1bca1b [ruby/json] Fix unused-but-set-global warnings in clang-23
https://github.com/ruby/json/commit/168095ccb8
2026-06-25 08:57:57 +00:00
Jean Boussier a746dd8374 [ruby/json] Declare types as RUBY_TYPED_THREAD_SAFE_FREE
Ref: https://github.com/ruby/ruby/pull/17395

https://github.com/ruby/json/commit/fa69e94b66
2026-06-25 06:37:32 +00:00
Sutou Kouhei f400c872d7 [ruby/json] Fix wrong call-seq for JSON::ResumableParser#eos?
https://github.com/ruby/json/commit/e1ee4b0e22
2026-06-25 06:35:23 +00:00
Masataka Pocke KuwabaraandClaude Opus 4.8 2f65ce7ccd [ruby/json] Preserve UTF-8 encoding when reallocating a frozen ResumableParser buffer
`JSON::ResumableParser#<<` raises `Encoding::CompatibilityError` when the
first chunk is a frozen multibyte UTF-8 string and more data is fed after a
partial parse.

## Reproduction

```ruby
require "json"

parser = JSON::ResumableParser.new
parser << '{"message":"日本'.freeze   # frozen + multibyte (UTF-8)
parser.parse                          # => false (incomplete)
parser << '語のつづき"}'.freeze       # => Encoding::CompatibilityError: BINARY and UTF-8
```

The error only surfaces when the first chunk is both frozen and multibyte.
A frozen ASCII-only first chunk stays ASCII-compatible and a mutable first
chunk keeps its encoding, so neither triggers it. This is common in
streaming use cases where chunks arrive as frozen UTF-8 (e.g. gRPC/protobuf
string fields), and is easily hit with multibyte (e.g. Japanese) payloads.

## Root cause

In `cResumableParser_feed`, the first feed adopts a frozen input string
directly as the buffer (`parser->buffer = str`), keeping its UTF-8 encoding.
When a later feed needs to reallocate that frozen buffer, it allocates the
new buffer with `rb_str_buf_new()`, which returns an ASCII-8BIT (BINARY)
string and does not carry over the original UTF-8 encoding. The subsequent
`rb_str_append(parser->buffer, str)` then appends a multibyte UTF-8 chunk to
a BINARY buffer that already holds non-ASCII bytes, and the two
encodings are incompatible.

`convert_encoding` always normalizes the input to UTF-8, so the buffer is
guaranteed to be UTF-8. Associating the freshly allocated buffer with UTF-8
restores that invariant. This mirrors the existing idiom used for the
generator buffer, and also fixes the latent case where a frozen ASCII-only
first chunk would silently leave the buffer as BINARY.

## Test

Added a regression test feeding frozen multibyte chunks across a partial
parse. The byte-by-byte `assert_resumed_parsing` helper cannot cover this:
it feeds single ASCII-8BIT bytes via `byte.chr`, which `convert_encoding`
turns into a mutable dup, so the buffer never becomes frozen and the
reallocation path is never taken. The `.freeze` calls are kept explicit so
the test still exercises the frozen path even if the file's
`frozen_string_literal` magic comment is ever removed.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PpeQSEFkF1X1Uuzjx9BsYe

https://github.com/ruby/json/commit/532065c259
2026-06-22 07:51:34 +00:00
Benoit Daloze fe7f1fd722 [ruby/json] Update extconf.rb guard to use RUBY_ENGINE_VERSION
https://github.com/ruby/json/commit/7c8af4b9ef
2026-06-21 12:04:03 +00:00
Jean Boussier d57e5f41a1 [ruby/json] Cleanup the rb_catch_obj workaround
Followup: https://github.com/ruby/json/pull/1031

Now that the bug has been fixed upstream, we can only apply
it on unpatched version.

Also refactor the workaround to expose the exact same API
as `rb_catch_obj`, making it easier to ignore and remove later.

https://github.com/ruby/json/commit/2afd1a9a61
2026-06-21 08:25:05 +00:00
Jean Boussier 34d51d56e2 [ruby/json] Simplify parser_config_init
https://github.com/ruby/json/commit/98925145d2
2026-06-20 13:24:31 +00:00
Jean Boussier 827bd3e0fd [ruby/json] ResumableParser: eagerly drop the buffer when reaching EOS
No point to wait for the next call to `<<` to remove the reference.

https://github.com/ruby/json/commit/b30a8f852d
2026-06-19 14:21:11 +00:00
Jean Boussier a7022d3357 [ruby/json] ResumableParser: accept only keyword arguments
Fix: https://github.com/ruby/json/pull/1016#issuecomment-4744487710

`json` takes option hashes across the board, mostly because its API
predates the introduction of keyword arguments.

I'd like to change that to only take keyword arguments and error
when an unknown argument is passed, but I'm not yet sure of the
backward compatibility consequences, so it might wait for the next
major.

But in the meantime, `ResumableParser` being a new API, it can
safely use keyword arguments.

https://github.com/ruby/json/commit/f08c66338c
2026-06-19 12:15:39 +00:00
Jean Boussier b6bcd73e9f [ruby/json] Workaround TruffleRuby buggy rb_catch_obj implementation
Somehow on TruffleRuby `rb_catch_obj` straight out doesn't call
the passed function, acting as a noop.

https://github.com/ruby/json/commit/9d8efcb08b
2026-06-19 11:37:07 +00:00
Jean Boussier 70abf87ed1 [ruby/json] ResumableParser: use throw rather than raise for handled EOS
Since the exception will be swallowed, building a message and
backtrace is just a waste of time.

https://github.com/ruby/json/commit/4bd1e9bce6
2026-06-19 11:37:07 +00:00
Jean Boussier 46128b15d5 [ruby/json] ResumableParser: Don't compute lines and columns on parse error
Fix: https://github.com/ruby/json/issues/1022

They can't always be accurate because we don't always keep the
full document in the buffer. As such it's better never to compute
them than to sometimes provide wrong coordinates.

In theory we could keep the number of lines since the start
of the parse, but that's more book keeping for little utility.

Anyway, these are useful to find a syntax error in a file,
not so much in a stream of documents.

https://github.com/ruby/json/commit/b2372115af
2026-06-19 08:45:41 +00:00
Jean Boussier 73cd91b816 [ruby/json] Implement ResumableParser#parsed_bytes
This is intended to make it easier to securely parse untrusted
inputs.

https://github.com/ruby/json/commit/21c2bbea1c
2026-06-19 08:23:43 +00:00
Jean Boussier 062228c046 [ruby/json] Refactor ResumableParser
Stop updating stack handles, we don't actually need them as we're
starting heap allocated. Add an assertion to prove it.

Use a dedicated field for storing the parser reference to be
used for marking EOS errors. Also stop marking EOS errors when
not in resumable mode.

https://github.com/ruby/json/commit/004a5c1faa
2026-06-18 22:19:14 +00:00
Yusuke EndohandClaude Opus 4.8 75bdfeb67a [ruby/json] Document that JSON::ResumableParser does not bound its buffer size
An incomplete document is buffered in full with no size limit, so reading from an
untrusted source can grow memory without bound. Note in the rdoc that bounding the
input is the caller's responsibility.

https://github.com/ruby/json/commit/3650e2bf41

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-18 19:37:53 +00:00
Jean BoussierandYusuke Endoh cde9c8a262 [ruby/json] ResumableParser#parse: don't swallow other parsers errors
Fix: https://github.com/ruby/json/issues/1024#issuecomment-4745232736

https://github.com/ruby/json/commit/65549acb9c

Co-Authored-By: Yusuke Endoh <mame@ruby-lang.org>
2026-06-18 19:32:16 +00:00
Yusuke EndohandJean Boussier 0c992796dd [ruby/json] Fix off-by-one line number in parse error messages
cursor_position consumed the newline ending the previous line (post-decrement)
before counting lines, so any error past the first line was reported one line
too low (e.g. "[1,\n@" reported '@' at line 1 instead of line 2). Count that
newline when the column loop breaks on it.

https://github.com/ruby/json/commit/82b6b21354

Co-Authored-By: Jean Boussier <jean.boussier@gmail.com>
2026-06-18 18:40:54 +00:00
Yusuke EndohandClaude Opus 4.8 8c7c8cb9e4 [ruby/json] Reset all per-parse state in JSON::ResumableParser#clear
https://github.com/ruby/json/commit/56d16deb46

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-18 17:27:58 +00:00
Yusuke EndohandJean Boussier 7215da1a63 [ruby/json] Fix JSON::ResumableParser stalling on a NUL byte
https://github.com/ruby/json/commit/d91b370b1e

Co-Authored-By: Jean Boussier <jean.boussier@gmail.com>
2026-06-18 17:25:28 +00:00
Yusuke EndohandClaude Opus 4.8 f322dd1332 [ruby/json] Forbid JSON::ResumableParser#<< while the parser is in use
https://github.com/ruby/json/commit/489b8c13b9

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-18 17:16:50 +00:00
Yusuke EndohandClaude Opus 4.8 0416fb209e [ruby/json] Fix JSON::ResumableParser#parse leaking the in_use lock on an empty buffer
https://github.com/ruby/json/commit/3927c5ca4c

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-18 16:02:29 +00:00
Yusuke EndohandClaude Opus 4.8 6a066f58bc [ruby/json] Fix heap-use-after-free in JSON::ResumableParser#partial_value
https://github.com/ruby/json/commit/e5fa06aa42

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-18 16:02:04 +00:00