[ruby/json] fbuffer.c: add debug mode with bound checks.

This would have caught https://github.com/ruby/json/pull/808
on CI.

https://github.com/ruby/json/commit/8109421fb4
This commit is contained in:
Jean Boussier 2025-05-23 11:27:29 +02:00 committed by Hiroshi SHIBATA
parent f171a263f7
commit 212213a552
No known key found for this signature in database
GPG Key ID: F9CF13417264FAC2
3 changed files with 43 additions and 8 deletions

View File

@ -36,6 +36,12 @@ typedef unsigned char _Bool;
# define MAYBE_UNUSED(x) x
#endif
#ifdef RUBY_DEBUG
#ifndef JSON_DEBUG
#define JSON_DEBUG RUBY_DEBUG
#endif
#endif
enum fbuffer_type {
FBUFFER_HEAP_ALLOCATED = 0,
FBUFFER_STACK_ALLOCATED = 1,
@ -46,6 +52,9 @@ typedef struct FBufferStruct {
unsigned long initial_length;
unsigned long len;
unsigned long capa;
#ifdef JSON_DEBUG
unsigned long requested;
#endif
char *ptr;
VALUE io;
} FBuffer;
@ -74,6 +83,20 @@ static void fbuffer_stack_init(FBuffer *fb, unsigned long initial_length, char *
fb->ptr = stack_buffer;
fb->capa = stack_buffer_size;
}
#ifdef JSON_DEBUG
fb->requested = 0;
#endif
}
static inline void fbuffer_consumed(FBuffer *fb, unsigned long consumed)
{
#ifdef JSON_DEBUG
if (consumed > fb->requested) {
rb_bug("fbuffer: Out of bound write");
}
fb->requested = 0;
#endif
fb->len += consumed;
}
static void fbuffer_free(FBuffer *fb)
@ -137,6 +160,10 @@ static void fbuffer_do_inc_capa(FBuffer *fb, unsigned long requested)
static inline void fbuffer_inc_capa(FBuffer *fb, unsigned long requested)
{
#ifdef JSON_DEBUG
fb->requested = requested;
#endif
if (RB_UNLIKELY(requested > fb->capa - fb->len)) {
fbuffer_do_inc_capa(fb, requested);
}
@ -147,15 +174,22 @@ static void fbuffer_append(FBuffer *fb, const char *newstr, unsigned long len)
if (len > 0) {
fbuffer_inc_capa(fb, len);
MEMCPY(fb->ptr + fb->len, newstr, char, len);
fb->len += len;
fbuffer_consumed(fb, len);
}
}
/* Appends a character into a buffer. The buffer needs to have sufficient capacity, via fbuffer_inc_capa(...). */
static inline void fbuffer_append_reserved_char(FBuffer *fb, char chr)
{
#ifdef JSON_DEBUG
if (fb->requested < 1) {
rb_bug("fbuffer: unreserved write");
}
fb->requested--;
#endif
fb->ptr[fb->len] = chr;
fb->len += 1;
fb->len++;
}
static void fbuffer_append_str(FBuffer *fb, VALUE str)
@ -172,7 +206,7 @@ static inline void fbuffer_append_char(FBuffer *fb, char newchr)
{
fbuffer_inc_capa(fb, 1);
*(fb->ptr + fb->len) = newchr;
fb->len++;
fbuffer_consumed(fb, 1);
}
static inline char *fbuffer_cursor(FBuffer *fb)
@ -182,7 +216,7 @@ static inline char *fbuffer_cursor(FBuffer *fb)
static inline void fbuffer_advance_to(FBuffer *fb, char *end)
{
fb->len = end - fb->ptr;
fbuffer_consumed(fb, (end - fb->ptr) - fb->len);
}
/*

View File

@ -4,8 +4,9 @@ if RUBY_ENGINE == 'truffleruby'
# The pure-Ruby generator is faster on TruffleRuby, so skip compiling the generator extension
File.write('Makefile', dummy_makefile("").join)
else
append_cflags("-std=c99")
append_cflags("-std=c99 -O0")
$defs << "-DJSON_GENERATOR"
$defs << "-DJSON_DEBUG" if ENV["JSON_DEBUG"]
if enable_config('generator-use-simd', default=!ENV["JSON_DISABLE_SIMD"])
if RbConfig::CONFIG['host_cpu'] =~ /^(arm.*|aarch64.*)/

View File

@ -404,7 +404,7 @@ static inline unsigned char search_escape_basic_neon(search_state *search)
if (!mask) {
// Nothing to escape, ensure search_flush doesn't do anything by setting
// search->cursor to search->ptr.
search->buffer->len += remaining;
fbuffer_consumed(search->buffer, remaining);
search->ptr = search->end;
search->cursor = search->end;
return 0;
@ -511,7 +511,7 @@ static inline TARGET_SSE2 FORCE_INLINE unsigned char search_escape_basic_sse2(se
if (needs_escape_mask == 0) {
// Nothing to escape, ensure search_flush doesn't do anything by setting
// search->cursor to search->ptr.
search->buffer->len += remaining;
fbuffer_consumed(search->buffer, remaining);
search->ptr = search->end;
search->cursor = search->end;
return 0;
@ -1415,7 +1415,7 @@ static void generate_json_float(FBuffer *buffer, struct generate_json_data *data
/* fpconv_dtoa converts a float to its shortest string representation,
* but it adds a ".0" if this is a plain integer.
*/
buffer->len += len;
fbuffer_consumed(buffer, len);
}
static void generate_json_fragment(FBuffer *buffer, struct generate_json_data *data, VALUE obj)