[ruby/json] 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.

https://github.com/ruby/json/commit/994859916a
This commit is contained in:
Jean Boussier 2025-01-16 14:53:51 +01:00 committed by Hiroshi SHIBATA
parent 99e9eb5380
commit e4b54b0a36
Notes: git 2025-01-20 07:09:22 +00:00
2 changed files with 67 additions and 42 deletions

View File

@ -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

View File

@ -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 <cstdbool>
# endif
#elif defined(HAVE_STDBOOL_H)
# include <stdbool.h>
#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;
}