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
32 changes: 32 additions & 0 deletions ext/json/ext/json.h
Original file line number Diff line number Diff line change
Expand Up @@ -144,4 +144,36 @@ static inline void *ruby_xrealloc2_sized(void *ptr, size_t new_elems, size_t ele
#define JSON_CPU_LITTLE_ENDIAN_64BITS 0
#endif

#ifdef JSON_TRUFFLERUBY_RB_CATCH_BUG

#undef RB_BLOCK_CALL_FUNC_ARGLIST
#define RB_BLOCK_CALL_FUNC_ARGLIST(yielded_arg, func_args) VALUE func_args

NORETURN(static inline) void json_rb_throw_obj(VALUE tag, VALUE obj)
{
VALUE exc = rb_exc_new_str(rb_eException, rb_utf8_str_new_cstr("throw_workaround"));
rb_ivar_set(exc, rb_intern("@throw_tag"), tag);
rb_ivar_set(exc, rb_intern("@throw_obj"), obj);
rb_exc_raise(exc);
}
#define rb_throw_obj json_rb_throw_obj

static inline VALUE json_rb_catch_obj(VALUE tag, VALUE (*func)(VALUE args), VALUE func_args)
{
int status;
VALUE result = rb_protect(func, func_args, &status);
if (status) {
VALUE exc = rb_errinfo();
if (tag == rb_ivar_get(exc, rb_intern("@throw_tag"))) {
rb_set_errinfo(Qnil);
return rb_ivar_get(exc, rb_intern("@throw_obj"));
}
rb_jump_tag(status);
}
return result;
}
#define rb_catch_obj json_rb_catch_obj

#endif // JSON_TRUFFLERUBY_RB_CATCH_BUG

#endif // _JSON_H_
7 changes: 6 additions & 1 deletion ext/json/ext/parser/extconf.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,12 @@
require 'mkmf'

$defs << "-DJSON_DEBUG" if ENV.fetch("JSON_DEBUG", "0") != "0"
$defs << "-DJSON_WORKAROUND_RB_CATCH_BUG" if RUBY_ENGINE == 'truffleruby'

if RUBY_ENGINE == 'truffleruby' && RUBY_VERSION < '4.0'

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FYI @eregon, let me know if the logic ain't correct here, I'm not very familiar with Truffle

@eregon eregon Jun 21, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That works (in both cases the workaround is not applied on truffleruby-head which is now fixed) but I think RUBY_ENGINE_VERSION is little bit cleaner (explicitly mentions the version fixing the bug)

Suggested change
if RUBY_ENGINE == 'truffleruby' && RUBY_VERSION < '4.0'
if RUBY_ENGINE == 'truffleruby' && RUBY_ENGINE_VERSION < '40.0'

#1036

# Ref: https://github.com/truffleruby/truffleruby/issues/4329
# Ref: https://github.com/truffleruby/truffleruby/pull/4333
$defs << "-DJSON_TRUFFLERUBY_RB_CATCH_BUG"
end

have_func("rb_enc_interned_str", "ruby/encoding.h") # RUBY_VERSION >= 3.0
have_func("rb_str_to_interned_str", "ruby.h") # RUBY_VERSION >= 3.0
Expand Down
36 changes: 3 additions & 33 deletions ext/json/ext/parser/parser.c
Original file line number Diff line number Diff line change
Expand Up @@ -672,43 +672,13 @@ static VALUE parse_error_new(JSON_ParserState *state, VALUE message, long line,
return exc;
}

#ifdef JSON_WORKAROUND_RB_CATCH_BUG
#define JSON_CATCH_FUNC_ARGLIST(yielded_arg, func_args) VALUE func_args

NORETURN(static) void parser_throw_eos(VALUE parser)
{
VALUE exc = rb_exc_new_str(eParserError, rb_utf8_str_new_cstr("EOS"));
rb_ivar_set(exc, rb_intern("@resumable_parser_eos"), parser);
rb_exc_raise(exc);
}

static VALUE parser_catch_eos(VALUE parser, VALUE (*func)(VALUE args), VALUE func_args)
{
int status;
VALUE result = rb_protect(func, func_args, &status);
if (status) {
VALUE error_source = rb_ivar_get(rb_errinfo(), rb_intern("@resumable_parser_eos"));
if (error_source == parser) {
rb_set_errinfo(Qnil);
return parser;
}
rb_jump_tag(status);
}
return result;
}
#else
#define JSON_CATCH_FUNC_ARGLIST RB_BLOCK_CALL_FUNC_ARGLIST
#define parser_throw_eos(parser) rb_throw_obj(parser, parser)
#define parser_catch_eos(parser, func, func_args) rb_catch_obj(parser, func, func_args)
#endif

NORETURN(static) void raise_parse_error(const char *format, JSON_ParserState *state, bool eos)
{
if (state->parser) {
if (eos) {
// the error will be swallowed by ResumableParser#parse, so no
// point building a message or backtrace.
parser_throw_eos(state->parser);
rb_throw_obj(state->parser, state->parser);
} else {
// line and columns can't be accurate in resumable
rb_exc_raise(parse_error_new(state, build_parse_error_message(format, state), 0, 0, eos));
Expand Down Expand Up @@ -2450,7 +2420,7 @@ struct json_parse_any_args {
VALUE parser;
};

static VALUE json_parse_any_resumable_safe0(JSON_CATCH_FUNC_ARGLIST(yielded_arg, _args))
static VALUE json_parse_any_resumable_safe0(RB_BLOCK_CALL_FUNC_ARGLIST(yielded_arg, _args))
{
struct json_parse_any_args *args = (struct json_parse_any_args *)_args;
return (VALUE)json_parse_any(args->state, args->config, true);
Expand All @@ -2459,7 +2429,7 @@ static VALUE json_parse_any_resumable_safe0(JSON_CATCH_FUNC_ARGLIST(yielded_arg,
static VALUE json_parse_any_resumable_safe(VALUE _args)
{
struct json_parse_any_args *args = (struct json_parse_any_args *)_args;
VALUE result = parser_catch_eos(args->parser, json_parse_any_resumable_safe0, _args);
VALUE result = rb_catch_obj(args->parser, json_parse_any_resumable_safe0, _args);
return result == args->parser ? Qfalse : result;
}

Expand Down
Loading