From 994859916aa25d894e9e89a818760ea93a8c023c Mon Sep 17 00:00:00 2001 From: Jean Boussier Date: Thu, 16 Jan 2025 14:53:51 +0100 Subject: [PATCH] Replace fbuffer by stack buffers or RB_ALLOCV in parser.c We only use that buffer for parsing integer and floats, these are unlikely to be very big, and if so we can just use RB_ALLOCV as it will almost always end in a small `alloca`. This allow to no longer need `rb_protect` around the parser. --- ext/json/ext/fbuffer/fbuffer.h | 11 +--- ext/json/ext/parser/parser.c | 98 +++++++++++++++++++++++----------- 2 files changed, 67 insertions(+), 42 deletions(-) diff --git a/ext/json/ext/fbuffer/fbuffer.h b/ext/json/ext/fbuffer/fbuffer.h index 0774c7e4..4c42e14b 100644 --- a/ext/json/ext/fbuffer/fbuffer.h +++ b/ext/json/ext/fbuffer/fbuffer.h @@ -59,17 +59,11 @@ typedef struct FBufferStruct { #define FBUFFER_PAIR(fb) FBUFFER_PTR(fb), FBUFFER_LEN(fb) static void fbuffer_free(FBuffer *fb); -#ifndef JSON_GENERATOR static void fbuffer_clear(FBuffer *fb); -#endif static void fbuffer_append(FBuffer *fb, const char *newstr, unsigned long len); -#ifdef JSON_GENERATOR static void fbuffer_append_long(FBuffer *fb, long number); -#endif static inline void fbuffer_append_char(FBuffer *fb, char newchr); -#ifdef JSON_GENERATOR static VALUE fbuffer_finalize(FBuffer *fb); -#endif static void fbuffer_stack_init(FBuffer *fb, unsigned long initial_length, char *stack_buffer, long stack_buffer_size) { @@ -156,7 +150,6 @@ static void fbuffer_append(FBuffer *fb, const char *newstr, unsigned long len) } } -#ifdef JSON_GENERATOR static void fbuffer_append_str(FBuffer *fb, VALUE str) { const char *newstr = StringValuePtr(str); @@ -166,7 +159,6 @@ static void fbuffer_append_str(FBuffer *fb, VALUE str) fbuffer_append(fb, newstr, len); } -#endif static inline void fbuffer_append_char(FBuffer *fb, char newchr) { @@ -175,7 +167,6 @@ static inline void fbuffer_append_char(FBuffer *fb, char newchr) fb->len++; } -#ifdef JSON_GENERATOR static long fltoa(long number, char *buf) { static const char digits[] = "0123456789"; @@ -210,5 +201,5 @@ static VALUE fbuffer_finalize(FBuffer *fb) return result; } } -#endif + #endif diff --git a/ext/json/ext/parser/parser.c b/ext/json/ext/parser/parser.c index e86d5c7b..f777d763 100644 --- a/ext/json/ext/parser/parser.c +++ b/ext/json/ext/parser/parser.c @@ -1,5 +1,32 @@ #include "ruby.h" -#include "../fbuffer/fbuffer.h" +#include "ruby/encoding.h" + +/* shims */ +/* This is the fallback definition from Ruby 3.4 */ + +#ifndef RBIMPL_STDBOOL_H +#if defined(__cplusplus) +# if defined(HAVE_STDBOOL_H) && (__cplusplus >= 201103L) +# include +# endif +#elif defined(HAVE_STDBOOL_H) +# include +#elif !defined(HAVE__BOOL) +typedef unsigned char _Bool; +# define bool _Bool +# define true ((_Bool)+1) +# define false ((_Bool)+0) +# define __bool_true_false_are_defined +#endif +#endif + +#ifndef RB_UNLIKELY +#define RB_UNLIKELY(expr) expr +#endif + +#ifndef RB_LIKELY +#define RB_LIKELY(expr) expr +#endif static VALUE mJSON, eNestingError, Encoding_UTF_8; static VALUE CNaN, CInfinity, CMinusInfinity; @@ -401,7 +428,6 @@ typedef struct JSON_ParserStateStruct { VALUE stack_handle; const char *cursor; const char *end; - FBuffer fbuffer; rvalue_stack *stack; rvalue_cache name_cache; int in_array; @@ -690,26 +716,44 @@ static inline VALUE fast_decode_integer(const char *p, const char *pe) return LL2NUM(memo); } -static VALUE +static VALUE json_decode_large_integer(const char *start, long len) +{ + VALUE buffer_v; + char *buffer = RB_ALLOCV_N(char, buffer_v, len + 1); + MEMCPY(buffer, start, char, len); + buffer[len] = '\0'; + VALUE number = rb_cstr2inum(buffer, 10); + RB_ALLOCV_END(buffer_v); + return number; +} + +static inline VALUE json_decode_integer(JSON_ParserState *state, const char *start, const char *end) { long len = end - start; if (RB_LIKELY(len < MAX_FAST_INTEGER_SIZE)) { return fast_decode_integer(start, end); } - - fbuffer_clear(&state->fbuffer); - fbuffer_append(&state->fbuffer, start, len); - fbuffer_append_char(&state->fbuffer, '\0'); - return rb_cstr2inum(FBUFFER_PTR(&state->fbuffer), 10); + return json_decode_large_integer(start, len); } +static VALUE json_decode_large_float(const char *start, long len) +{ + VALUE buffer_v; + char *buffer = RB_ALLOCV_N(char, buffer_v, len + 1); + MEMCPY(buffer, start, char, len); + buffer[len] = '\0'; + VALUE number = DBL2NUM(rb_cstr_to_dbl(buffer, 1)); + RB_ALLOCV_END(buffer_v); + return number; +} + static VALUE json_decode_float(JSON_ParserState *state, const char *start, const char *end) { VALUE mod = Qnil; ID method_id = 0; JSON_ParserConfig *config = state->config; - if (config->decimal_class) { + if (RB_UNLIKELY(config->decimal_class)) { // TODO: we should move this to the constructor if (rb_respond_to(config->decimal_class, i_try_convert)) { mod = config->decimal_class; @@ -739,15 +783,17 @@ static VALUE json_decode_float(JSON_ParserState *state, const char *start, const } long len = end - start; - fbuffer_clear(&state->fbuffer); - fbuffer_append(&state->fbuffer, start, len); - fbuffer_append_char(&state->fbuffer, '\0'); - if (method_id) { - VALUE text = rb_str_new2(FBUFFER_PTR(&state->fbuffer)); + if (RB_UNLIKELY(method_id)) { + VALUE text = rb_str_new(start, len); return rb_funcallv(mod, method_id, 1, &text); + } else if (RB_LIKELY(len < 64)) { + char buffer[64]; + MEMCPY(buffer, start, char, len); + buffer[len] = '\0'; + return DBL2NUM(rb_cstr_to_dbl(buffer, 1)); } else { - return DBL2NUM(rb_cstr_to_dbl(FBUFFER_PTR(&state->fbuffer), 1)); + return json_decode_large_float(start, len); } } @@ -1283,14 +1329,6 @@ static VALUE cParserConfig_initialize(VALUE self, VALUE opts) return self; } -static VALUE cParser_parse_safe(VALUE vstate) -{ - JSON_ParserState *state = (JSON_ParserState *)vstate; - VALUE result = json_parse_any(state); - json_ensure_eof(state); - return result; -} - static VALUE cParser_parse(JSON_ParserConfig *config, VALUE Vsource) { Vsource = convert_encoding(StringValue(Vsource)); @@ -1311,17 +1349,13 @@ static VALUE cParser_parse(JSON_ParserConfig *config, VALUE Vsource) }; JSON_ParserState *state = &_state; - char stack_buffer[FBUFFER_STACK_SIZE]; - fbuffer_stack_init(&state->fbuffer, FBUFFER_INITIAL_LENGTH_DEFAULT, stack_buffer, FBUFFER_STACK_SIZE); - - int interupted; - VALUE result = rb_protect(cParser_parse_safe, (VALUE)state, &interupted); + VALUE result = json_parse_any(state); + // This may be skipped in case of exception, but + // it won't cause a leak. rvalue_stack_eagerly_release(state->stack_handle); - fbuffer_free(&state->fbuffer); - if (interupted) { - rb_jump_tag(interupted); - } + + json_ensure_eof(state); return result; }