json 2.15.2 → 2.16.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGES.md +10 -0
- data/LEGAL +12 -0
- data/README.md +17 -1
- data/ext/json/ext/fbuffer/fbuffer.h +3 -52
- data/ext/json/ext/generator/generator.c +157 -133
- data/ext/json/ext/json.h +92 -0
- data/ext/json/ext/parser/extconf.rb +2 -0
- data/ext/json/ext/parser/parser.c +400 -316
- data/ext/json/ext/simd/simd.h +15 -12
- data/ext/json/ext/vendor/ryu.h +819 -0
- data/lib/json/common.rb +7 -12
- data/lib/json/ext/generator/state.rb +4 -0
- data/lib/json/truffle_ruby/generator.rb +31 -7
- data/lib/json/version.rb +1 -1
- metadata +3 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: d8bd00bbc63367659eb90ee7e3f4f1394fecdf713fc724f7908c6a2c7b79bbf7
|
|
4
|
+
data.tar.gz: efa1d81f687277247649de777f25be01e56ad8dfac1bdd9ce61111e10231185e
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: cb8b505c08b88115a87aa0b6ab8b7f34d6e0611af7d37277cef243131990db6cb77ca2508045104588607d54bd58b0c2e67ce0728bd72c4c32d2d77a56b9762e
|
|
7
|
+
data.tar.gz: 8dec0d5ba99f33e68049328f060a600da2fe0545caf47c44c2e812a8316cbe64a99152f365be8654cb4af45493de3bed97baa61a0b2a71ae71a7466d0ee11e8c
|
data/CHANGES.md
CHANGED
|
@@ -2,6 +2,16 @@
|
|
|
2
2
|
|
|
3
3
|
### Unreleased
|
|
4
4
|
|
|
5
|
+
### 2025-11-07 (2.16.0)
|
|
6
|
+
|
|
7
|
+
* Deprecate `JSON::State#[]` and `JSON::State#[]=`. Consider using `JSON::Coder` instead.
|
|
8
|
+
* `JSON::Coder` now also yields to the block when encountering strings with invalid encoding.
|
|
9
|
+
* Fix GeneratorError messages to be UTF-8 encoded.
|
|
10
|
+
* Fix memory leak when `Exception` is raised, or `throw` is used during JSON generation.
|
|
11
|
+
* Optimized floating point number parsing by integrating the ryu algorithm (thanks to Josef Šimánek).
|
|
12
|
+
* Optimized numbers parsing using SWAR (thanks to Scott Myron).
|
|
13
|
+
* Optimized parsing of pretty printed documents using SWAR (thanks to Scott Myron).
|
|
14
|
+
|
|
5
15
|
### 2025-10-25 (2.15.2)
|
|
6
16
|
|
|
7
17
|
* Fix `JSON::Coder` to have one dedicated depth counter per invocation.
|
data/LEGAL
CHANGED
|
@@ -6,3 +6,15 @@
|
|
|
6
6
|
All the files in this distribution are covered under either the Ruby's
|
|
7
7
|
license (see the file COPYING) or public-domain except some files
|
|
8
8
|
mentioned below.
|
|
9
|
+
|
|
10
|
+
ext/json/ext/vendor/fpconv.h::
|
|
11
|
+
This file is adapted from https://github.com/night-shift/fpconv
|
|
12
|
+
It is licensed under Boost Software License 1.0.
|
|
13
|
+
|
|
14
|
+
ext/json/ext/vendor/jeaiii-ltoa.h::
|
|
15
|
+
This file is adapted from https://github.com/jeaiii/itoa
|
|
16
|
+
It is licensed under the MIT License
|
|
17
|
+
|
|
18
|
+
ext/json/ext/vendor/ryu.h::
|
|
19
|
+
This file is adapted from the Ryu algorithm by Ulf Adams https://github.com/ulfjack/ryu.
|
|
20
|
+
It is dual-licensed under Apache License 2.0 OR Boost Software License 1.0.
|
data/README.md
CHANGED
|
@@ -113,7 +113,23 @@ puts MyApp::API_JSON_CODER.dump(Time.now.utc) # => "2025-01-21T08:41:44.286Z"
|
|
|
113
113
|
The provided block is called for all objects that don't have a native JSON equivalent, and
|
|
114
114
|
must return a Ruby object that has a native JSON equivalent.
|
|
115
115
|
|
|
116
|
-
It is also called for objects that do have a JSON equivalent, but are used as Hash keys, for instance `{ 1 => 2}
|
|
116
|
+
It is also called for objects that do have a JSON equivalent, but are used as Hash keys, for instance `{ 1 => 2}`,
|
|
117
|
+
as well as for strings that aren't valid UTF-8:
|
|
118
|
+
|
|
119
|
+
```ruby
|
|
120
|
+
coder = JSON::Combining.new do |object, is_object_key|
|
|
121
|
+
case object
|
|
122
|
+
when String
|
|
123
|
+
if !string.valid_encoding? || string.encoding != Encoding::UTF_8
|
|
124
|
+
Base64.encode64(string)
|
|
125
|
+
else
|
|
126
|
+
string
|
|
127
|
+
end
|
|
128
|
+
else
|
|
129
|
+
object
|
|
130
|
+
end
|
|
131
|
+
end
|
|
132
|
+
```
|
|
117
133
|
|
|
118
134
|
## Combining JSON fragments
|
|
119
135
|
|
|
@@ -1,55 +1,9 @@
|
|
|
1
1
|
#ifndef _FBUFFER_H_
|
|
2
2
|
#define _FBUFFER_H_
|
|
3
3
|
|
|
4
|
-
#include "
|
|
5
|
-
#include "ruby/encoding.h"
|
|
4
|
+
#include "../json.h"
|
|
6
5
|
#include "../vendor/jeaiii-ltoa.h"
|
|
7
6
|
|
|
8
|
-
/* shims */
|
|
9
|
-
/* This is the fallback definition from Ruby 3.4 */
|
|
10
|
-
|
|
11
|
-
#ifndef RBIMPL_STDBOOL_H
|
|
12
|
-
#if defined(__cplusplus)
|
|
13
|
-
# if defined(HAVE_STDBOOL_H) && (__cplusplus >= 201103L)
|
|
14
|
-
# include <cstdbool>
|
|
15
|
-
# endif
|
|
16
|
-
#elif defined(HAVE_STDBOOL_H)
|
|
17
|
-
# include <stdbool.h>
|
|
18
|
-
#elif !defined(HAVE__BOOL)
|
|
19
|
-
typedef unsigned char _Bool;
|
|
20
|
-
# define bool _Bool
|
|
21
|
-
# define true ((_Bool)+1)
|
|
22
|
-
# define false ((_Bool)+0)
|
|
23
|
-
# define __bool_true_false_are_defined
|
|
24
|
-
#endif
|
|
25
|
-
#endif
|
|
26
|
-
|
|
27
|
-
#ifndef NOINLINE
|
|
28
|
-
#if defined(__has_attribute) && __has_attribute(noinline)
|
|
29
|
-
#define NOINLINE() __attribute__((noinline))
|
|
30
|
-
#else
|
|
31
|
-
#define NOINLINE()
|
|
32
|
-
#endif
|
|
33
|
-
#endif
|
|
34
|
-
|
|
35
|
-
#ifndef RB_UNLIKELY
|
|
36
|
-
#define RB_UNLIKELY(expr) expr
|
|
37
|
-
#endif
|
|
38
|
-
|
|
39
|
-
#ifndef RB_LIKELY
|
|
40
|
-
#define RB_LIKELY(expr) expr
|
|
41
|
-
#endif
|
|
42
|
-
|
|
43
|
-
#ifndef MAYBE_UNUSED
|
|
44
|
-
# define MAYBE_UNUSED(x) x
|
|
45
|
-
#endif
|
|
46
|
-
|
|
47
|
-
#ifdef RUBY_DEBUG
|
|
48
|
-
#ifndef JSON_DEBUG
|
|
49
|
-
#define JSON_DEBUG RUBY_DEBUG
|
|
50
|
-
#endif
|
|
51
|
-
#endif
|
|
52
|
-
|
|
53
7
|
enum fbuffer_type {
|
|
54
8
|
FBUFFER_HEAP_ALLOCATED = 0,
|
|
55
9
|
FBUFFER_STACK_ALLOCATED = 1,
|
|
@@ -283,14 +237,11 @@ static VALUE fbuffer_finalize(FBuffer *fb)
|
|
|
283
237
|
{
|
|
284
238
|
if (fb->io) {
|
|
285
239
|
fbuffer_flush(fb);
|
|
286
|
-
fbuffer_free(fb);
|
|
287
240
|
rb_io_flush(fb->io);
|
|
288
241
|
return fb->io;
|
|
289
242
|
} else {
|
|
290
|
-
|
|
291
|
-
fbuffer_free(fb);
|
|
292
|
-
return result;
|
|
243
|
+
return rb_utf8_str_new(FBUFFER_PTR(fb), FBUFFER_LEN(fb));
|
|
293
244
|
}
|
|
294
245
|
}
|
|
295
246
|
|
|
296
|
-
#endif
|
|
247
|
+
#endif // _FBUFFER_H_
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
#include "
|
|
1
|
+
#include "../json.h"
|
|
2
2
|
#include "../fbuffer/fbuffer.h"
|
|
3
3
|
#include "../vendor/fpconv.c"
|
|
4
4
|
|
|
@@ -36,10 +36,6 @@ typedef struct JSON_Generator_StateStruct {
|
|
|
36
36
|
bool strict;
|
|
37
37
|
} JSON_Generator_State;
|
|
38
38
|
|
|
39
|
-
#ifndef RB_UNLIKELY
|
|
40
|
-
#define RB_UNLIKELY(cond) (cond)
|
|
41
|
-
#endif
|
|
42
|
-
|
|
43
39
|
static VALUE mJSON, cState, cFragment, eGeneratorError, eNestingError, Encoding_UTF_8;
|
|
44
40
|
|
|
45
41
|
static ID i_to_s, i_to_json, i_new, i_pack, i_unpack, i_create_id, i_extend, i_encode;
|
|
@@ -85,23 +81,18 @@ static void generate_json_fragment(FBuffer *buffer, struct generate_json_data *d
|
|
|
85
81
|
|
|
86
82
|
static int usascii_encindex, utf8_encindex, binary_encindex;
|
|
87
83
|
|
|
88
|
-
|
|
89
|
-
RBIMPL_ATTR_NORETURN()
|
|
90
|
-
#endif
|
|
91
|
-
static void raise_generator_error_str(VALUE invalid_object, VALUE str)
|
|
84
|
+
NORETURN(static void) raise_generator_error_str(VALUE invalid_object, VALUE str)
|
|
92
85
|
{
|
|
86
|
+
rb_enc_associate_index(str, utf8_encindex);
|
|
93
87
|
VALUE exc = rb_exc_new_str(eGeneratorError, str);
|
|
94
88
|
rb_ivar_set(exc, rb_intern("@invalid_object"), invalid_object);
|
|
95
89
|
rb_exc_raise(exc);
|
|
96
90
|
}
|
|
97
91
|
|
|
98
|
-
#ifdef RBIMPL_ATTR_NORETURN
|
|
99
|
-
RBIMPL_ATTR_NORETURN()
|
|
100
|
-
#endif
|
|
101
92
|
#ifdef RBIMPL_ATTR_FORMAT
|
|
102
93
|
RBIMPL_ATTR_FORMAT(RBIMPL_PRINTF_FORMAT, 2, 3)
|
|
103
94
|
#endif
|
|
104
|
-
static void raise_generator_error(VALUE invalid_object, const char *fmt, ...)
|
|
95
|
+
NORETURN(static void) raise_generator_error(VALUE invalid_object, const char *fmt, ...)
|
|
105
96
|
{
|
|
106
97
|
va_list args;
|
|
107
98
|
va_start(args, fmt);
|
|
@@ -136,13 +127,7 @@ typedef struct _search_state {
|
|
|
136
127
|
#endif /* HAVE_SIMD */
|
|
137
128
|
} search_state;
|
|
138
129
|
|
|
139
|
-
|
|
140
|
-
#define FORCE_INLINE __attribute__((always_inline))
|
|
141
|
-
#else
|
|
142
|
-
#define FORCE_INLINE
|
|
143
|
-
#endif
|
|
144
|
-
|
|
145
|
-
static inline FORCE_INLINE void search_flush(search_state *search)
|
|
130
|
+
static ALWAYS_INLINE() void search_flush(search_state *search)
|
|
146
131
|
{
|
|
147
132
|
// Do not remove this conditional without profiling, specifically escape-heavy text.
|
|
148
133
|
// escape_UTF8_char_basic will advance search->ptr and search->cursor (effectively a search_flush).
|
|
@@ -185,7 +170,7 @@ static inline unsigned char search_escape_basic(search_state *search)
|
|
|
185
170
|
return 0;
|
|
186
171
|
}
|
|
187
172
|
|
|
188
|
-
static
|
|
173
|
+
static ALWAYS_INLINE() void escape_UTF8_char_basic(search_state *search)
|
|
189
174
|
{
|
|
190
175
|
const unsigned char ch = (unsigned char)*search->ptr;
|
|
191
176
|
switch (ch) {
|
|
@@ -272,7 +257,7 @@ static inline void escape_UTF8_char(search_state *search, unsigned char ch_len)
|
|
|
272
257
|
|
|
273
258
|
#ifdef HAVE_SIMD
|
|
274
259
|
|
|
275
|
-
static
|
|
260
|
+
static ALWAYS_INLINE() char *copy_remaining_bytes(search_state *search, unsigned long vec_len, unsigned long len)
|
|
276
261
|
{
|
|
277
262
|
// Flush the buffer so everything up until the last 'len' characters are unflushed.
|
|
278
263
|
search_flush(search);
|
|
@@ -295,7 +280,7 @@ static inline FORCE_INLINE char *copy_remaining_bytes(search_state *search, unsi
|
|
|
295
280
|
|
|
296
281
|
#ifdef HAVE_SIMD_NEON
|
|
297
282
|
|
|
298
|
-
static
|
|
283
|
+
static ALWAYS_INLINE() unsigned char neon_next_match(search_state *search)
|
|
299
284
|
{
|
|
300
285
|
uint64_t mask = search->matches_mask;
|
|
301
286
|
uint32_t index = trailing_zeros64(mask) >> 2;
|
|
@@ -409,7 +394,7 @@ static inline unsigned char search_escape_basic_neon(search_state *search)
|
|
|
409
394
|
|
|
410
395
|
#ifdef HAVE_SIMD_SSE2
|
|
411
396
|
|
|
412
|
-
static
|
|
397
|
+
static ALWAYS_INLINE() unsigned char sse2_next_match(search_state *search)
|
|
413
398
|
{
|
|
414
399
|
int mask = search->matches_mask;
|
|
415
400
|
int index = trailing_zeros(mask);
|
|
@@ -433,7 +418,7 @@ static inline FORCE_INLINE unsigned char sse2_next_match(search_state *search)
|
|
|
433
418
|
#define TARGET_SSE2
|
|
434
419
|
#endif
|
|
435
420
|
|
|
436
|
-
static
|
|
421
|
+
static TARGET_SSE2 ALWAYS_INLINE() unsigned char search_escape_basic_sse2(search_state *search)
|
|
437
422
|
{
|
|
438
423
|
if (RB_UNLIKELY(search->has_matches)) {
|
|
439
424
|
// There are more matches if search->matches_mask > 0.
|
|
@@ -995,13 +980,12 @@ static inline VALUE vstate_get(struct generate_json_data *data)
|
|
|
995
980
|
return data->vstate;
|
|
996
981
|
}
|
|
997
982
|
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
};
|
|
983
|
+
static VALUE
|
|
984
|
+
json_call_as_json(JSON_Generator_State *state, VALUE object, VALUE is_key)
|
|
985
|
+
{
|
|
986
|
+
VALUE proc_args[2] = {object, is_key};
|
|
987
|
+
return rb_proc_call_with_block(state->as_json, 2, proc_args, Qnil);
|
|
988
|
+
}
|
|
1005
989
|
|
|
1006
990
|
static VALUE
|
|
1007
991
|
convert_string_subclass(VALUE key)
|
|
@@ -1018,6 +1002,129 @@ convert_string_subclass(VALUE key)
|
|
|
1018
1002
|
return key_to_s;
|
|
1019
1003
|
}
|
|
1020
1004
|
|
|
1005
|
+
static bool enc_utf8_compatible_p(int enc_idx)
|
|
1006
|
+
{
|
|
1007
|
+
if (enc_idx == usascii_encindex) return true;
|
|
1008
|
+
if (enc_idx == utf8_encindex) return true;
|
|
1009
|
+
return false;
|
|
1010
|
+
}
|
|
1011
|
+
|
|
1012
|
+
static VALUE encode_json_string_try(VALUE str)
|
|
1013
|
+
{
|
|
1014
|
+
return rb_funcall(str, i_encode, 1, Encoding_UTF_8);
|
|
1015
|
+
}
|
|
1016
|
+
|
|
1017
|
+
static VALUE encode_json_string_rescue(VALUE str, VALUE exception)
|
|
1018
|
+
{
|
|
1019
|
+
raise_generator_error_str(str, rb_funcall(exception, rb_intern("message"), 0));
|
|
1020
|
+
return Qundef;
|
|
1021
|
+
}
|
|
1022
|
+
|
|
1023
|
+
static inline bool valid_json_string_p(VALUE str)
|
|
1024
|
+
{
|
|
1025
|
+
int coderange = rb_enc_str_coderange(str);
|
|
1026
|
+
|
|
1027
|
+
if (RB_LIKELY(coderange == ENC_CODERANGE_7BIT)) {
|
|
1028
|
+
return true;
|
|
1029
|
+
}
|
|
1030
|
+
|
|
1031
|
+
if (RB_LIKELY(coderange == ENC_CODERANGE_VALID)) {
|
|
1032
|
+
return enc_utf8_compatible_p(RB_ENCODING_GET_INLINED(str));
|
|
1033
|
+
}
|
|
1034
|
+
|
|
1035
|
+
return false;
|
|
1036
|
+
}
|
|
1037
|
+
|
|
1038
|
+
static inline VALUE ensure_valid_encoding(struct generate_json_data *data, VALUE str, bool as_json_called, bool is_key)
|
|
1039
|
+
{
|
|
1040
|
+
if (RB_LIKELY(valid_json_string_p(str))) {
|
|
1041
|
+
return str;
|
|
1042
|
+
}
|
|
1043
|
+
|
|
1044
|
+
if (!as_json_called && data->state->strict && RTEST(data->state->as_json)) {
|
|
1045
|
+
VALUE coerced_str = json_call_as_json(data->state, str, Qfalse);
|
|
1046
|
+
if (coerced_str != str) {
|
|
1047
|
+
if (RB_TYPE_P(coerced_str, T_STRING)) {
|
|
1048
|
+
if (!valid_json_string_p(coerced_str)) {
|
|
1049
|
+
raise_generator_error(str, "source sequence is illegal/malformed utf-8");
|
|
1050
|
+
}
|
|
1051
|
+
} else {
|
|
1052
|
+
// as_json could return another type than T_STRING
|
|
1053
|
+
if (is_key) {
|
|
1054
|
+
raise_generator_error(coerced_str, "%"PRIsVALUE" not allowed as object key in JSON", CLASS_OF(coerced_str));
|
|
1055
|
+
}
|
|
1056
|
+
}
|
|
1057
|
+
|
|
1058
|
+
return coerced_str;
|
|
1059
|
+
}
|
|
1060
|
+
}
|
|
1061
|
+
|
|
1062
|
+
if (RB_ENCODING_GET_INLINED(str) == binary_encindex) {
|
|
1063
|
+
VALUE utf8_string = rb_enc_associate_index(rb_str_dup(str), utf8_encindex);
|
|
1064
|
+
switch (rb_enc_str_coderange(utf8_string)) {
|
|
1065
|
+
case ENC_CODERANGE_7BIT:
|
|
1066
|
+
return utf8_string;
|
|
1067
|
+
case ENC_CODERANGE_VALID:
|
|
1068
|
+
// For historical reason, we silently reinterpret binary strings as UTF-8 if it would work.
|
|
1069
|
+
// TODO: Raise in 3.0.0
|
|
1070
|
+
rb_warn("JSON.generate: UTF-8 string passed as BINARY, this will raise an encoding error in json 3.0");
|
|
1071
|
+
return utf8_string;
|
|
1072
|
+
break;
|
|
1073
|
+
}
|
|
1074
|
+
}
|
|
1075
|
+
|
|
1076
|
+
return rb_rescue(encode_json_string_try, str, encode_json_string_rescue, str);
|
|
1077
|
+
}
|
|
1078
|
+
|
|
1079
|
+
static void raw_generate_json_string(FBuffer *buffer, struct generate_json_data *data, VALUE obj)
|
|
1080
|
+
{
|
|
1081
|
+
fbuffer_append_char(buffer, '"');
|
|
1082
|
+
|
|
1083
|
+
long len;
|
|
1084
|
+
search_state search;
|
|
1085
|
+
search.buffer = buffer;
|
|
1086
|
+
RSTRING_GETMEM(obj, search.ptr, len);
|
|
1087
|
+
search.cursor = search.ptr;
|
|
1088
|
+
search.end = search.ptr + len;
|
|
1089
|
+
|
|
1090
|
+
#ifdef HAVE_SIMD
|
|
1091
|
+
search.matches_mask = 0;
|
|
1092
|
+
search.has_matches = false;
|
|
1093
|
+
search.chunk_base = NULL;
|
|
1094
|
+
#endif /* HAVE_SIMD */
|
|
1095
|
+
|
|
1096
|
+
switch (rb_enc_str_coderange(obj)) {
|
|
1097
|
+
case ENC_CODERANGE_7BIT:
|
|
1098
|
+
case ENC_CODERANGE_VALID:
|
|
1099
|
+
if (RB_UNLIKELY(data->state->ascii_only)) {
|
|
1100
|
+
convert_UTF8_to_ASCII_only_JSON(&search, data->state->script_safe ? script_safe_escape_table : ascii_only_escape_table);
|
|
1101
|
+
} else if (RB_UNLIKELY(data->state->script_safe)) {
|
|
1102
|
+
convert_UTF8_to_script_safe_JSON(&search);
|
|
1103
|
+
} else {
|
|
1104
|
+
convert_UTF8_to_JSON(&search);
|
|
1105
|
+
}
|
|
1106
|
+
break;
|
|
1107
|
+
default:
|
|
1108
|
+
raise_generator_error(obj, "source sequence is illegal/malformed utf-8");
|
|
1109
|
+
break;
|
|
1110
|
+
}
|
|
1111
|
+
fbuffer_append_char(buffer, '"');
|
|
1112
|
+
}
|
|
1113
|
+
|
|
1114
|
+
static void generate_json_string(FBuffer *buffer, struct generate_json_data *data, VALUE obj)
|
|
1115
|
+
{
|
|
1116
|
+
obj = ensure_valid_encoding(data, obj, false, false);
|
|
1117
|
+
raw_generate_json_string(buffer, data, obj);
|
|
1118
|
+
}
|
|
1119
|
+
|
|
1120
|
+
struct hash_foreach_arg {
|
|
1121
|
+
VALUE hash;
|
|
1122
|
+
struct generate_json_data *data;
|
|
1123
|
+
int first_key_type;
|
|
1124
|
+
bool first;
|
|
1125
|
+
bool mixed_keys_encountered;
|
|
1126
|
+
};
|
|
1127
|
+
|
|
1021
1128
|
NOINLINE()
|
|
1022
1129
|
static void
|
|
1023
1130
|
json_inspect_hash_with_mixed_keys(struct hash_foreach_arg *arg)
|
|
@@ -1034,13 +1141,6 @@ json_inspect_hash_with_mixed_keys(struct hash_foreach_arg *arg)
|
|
|
1034
1141
|
}
|
|
1035
1142
|
}
|
|
1036
1143
|
|
|
1037
|
-
static VALUE
|
|
1038
|
-
json_call_as_json(JSON_Generator_State *state, VALUE object, VALUE is_key)
|
|
1039
|
-
{
|
|
1040
|
-
VALUE proc_args[2] = {object, is_key};
|
|
1041
|
-
return rb_proc_call_with_block(state->as_json, 2, proc_args, Qnil);
|
|
1042
|
-
}
|
|
1043
|
-
|
|
1044
1144
|
static int
|
|
1045
1145
|
json_object_i(VALUE key, VALUE val, VALUE _arg)
|
|
1046
1146
|
{
|
|
@@ -1106,8 +1206,10 @@ json_object_i(VALUE key, VALUE val, VALUE _arg)
|
|
|
1106
1206
|
break;
|
|
1107
1207
|
}
|
|
1108
1208
|
|
|
1209
|
+
key_to_s = ensure_valid_encoding(data, key_to_s, as_json_called, true);
|
|
1210
|
+
|
|
1109
1211
|
if (RB_LIKELY(RBASIC_CLASS(key_to_s) == rb_cString)) {
|
|
1110
|
-
|
|
1212
|
+
raw_generate_json_string(buffer, data, key_to_s);
|
|
1111
1213
|
} else {
|
|
1112
1214
|
generate_json(buffer, data, key_to_s);
|
|
1113
1215
|
}
|
|
@@ -1190,85 +1292,6 @@ static void generate_json_array(FBuffer *buffer, struct generate_json_data *data
|
|
|
1190
1292
|
fbuffer_append_char(buffer, ']');
|
|
1191
1293
|
}
|
|
1192
1294
|
|
|
1193
|
-
static inline int enc_utf8_compatible_p(int enc_idx)
|
|
1194
|
-
{
|
|
1195
|
-
if (enc_idx == usascii_encindex) return 1;
|
|
1196
|
-
if (enc_idx == utf8_encindex) return 1;
|
|
1197
|
-
return 0;
|
|
1198
|
-
}
|
|
1199
|
-
|
|
1200
|
-
static VALUE encode_json_string_try(VALUE str)
|
|
1201
|
-
{
|
|
1202
|
-
return rb_funcall(str, i_encode, 1, Encoding_UTF_8);
|
|
1203
|
-
}
|
|
1204
|
-
|
|
1205
|
-
static VALUE encode_json_string_rescue(VALUE str, VALUE exception)
|
|
1206
|
-
{
|
|
1207
|
-
raise_generator_error_str(str, rb_funcall(exception, rb_intern("message"), 0));
|
|
1208
|
-
return Qundef;
|
|
1209
|
-
}
|
|
1210
|
-
|
|
1211
|
-
static inline VALUE ensure_valid_encoding(VALUE str)
|
|
1212
|
-
{
|
|
1213
|
-
int encindex = RB_ENCODING_GET(str);
|
|
1214
|
-
VALUE utf8_string;
|
|
1215
|
-
if (RB_UNLIKELY(!enc_utf8_compatible_p(encindex))) {
|
|
1216
|
-
if (encindex == binary_encindex) {
|
|
1217
|
-
utf8_string = rb_enc_associate_index(rb_str_dup(str), utf8_encindex);
|
|
1218
|
-
switch (rb_enc_str_coderange(utf8_string)) {
|
|
1219
|
-
case ENC_CODERANGE_7BIT:
|
|
1220
|
-
return utf8_string;
|
|
1221
|
-
case ENC_CODERANGE_VALID:
|
|
1222
|
-
// For historical reason, we silently reinterpret binary strings as UTF-8 if it would work.
|
|
1223
|
-
// TODO: Raise in 3.0.0
|
|
1224
|
-
rb_warn("JSON.generate: UTF-8 string passed as BINARY, this will raise an encoding error in json 3.0");
|
|
1225
|
-
return utf8_string;
|
|
1226
|
-
break;
|
|
1227
|
-
}
|
|
1228
|
-
}
|
|
1229
|
-
|
|
1230
|
-
str = rb_rescue(encode_json_string_try, str, encode_json_string_rescue, str);
|
|
1231
|
-
}
|
|
1232
|
-
return str;
|
|
1233
|
-
}
|
|
1234
|
-
|
|
1235
|
-
static void generate_json_string(FBuffer *buffer, struct generate_json_data *data, VALUE obj)
|
|
1236
|
-
{
|
|
1237
|
-
obj = ensure_valid_encoding(obj);
|
|
1238
|
-
|
|
1239
|
-
fbuffer_append_char(buffer, '"');
|
|
1240
|
-
|
|
1241
|
-
long len;
|
|
1242
|
-
search_state search;
|
|
1243
|
-
search.buffer = buffer;
|
|
1244
|
-
RSTRING_GETMEM(obj, search.ptr, len);
|
|
1245
|
-
search.cursor = search.ptr;
|
|
1246
|
-
search.end = search.ptr + len;
|
|
1247
|
-
|
|
1248
|
-
#ifdef HAVE_SIMD
|
|
1249
|
-
search.matches_mask = 0;
|
|
1250
|
-
search.has_matches = false;
|
|
1251
|
-
search.chunk_base = NULL;
|
|
1252
|
-
#endif /* HAVE_SIMD */
|
|
1253
|
-
|
|
1254
|
-
switch (rb_enc_str_coderange(obj)) {
|
|
1255
|
-
case ENC_CODERANGE_7BIT:
|
|
1256
|
-
case ENC_CODERANGE_VALID:
|
|
1257
|
-
if (RB_UNLIKELY(data->state->ascii_only)) {
|
|
1258
|
-
convert_UTF8_to_ASCII_only_JSON(&search, data->state->script_safe ? script_safe_escape_table : ascii_only_escape_table);
|
|
1259
|
-
} else if (RB_UNLIKELY(data->state->script_safe)) {
|
|
1260
|
-
convert_UTF8_to_script_safe_JSON(&search);
|
|
1261
|
-
} else {
|
|
1262
|
-
convert_UTF8_to_JSON(&search);
|
|
1263
|
-
}
|
|
1264
|
-
break;
|
|
1265
|
-
default:
|
|
1266
|
-
raise_generator_error(obj, "source sequence is illegal/malformed utf-8");
|
|
1267
|
-
break;
|
|
1268
|
-
}
|
|
1269
|
-
fbuffer_append_char(buffer, '"');
|
|
1270
|
-
}
|
|
1271
|
-
|
|
1272
1295
|
static void generate_json_fallback(FBuffer *buffer, struct generate_json_data *data, VALUE obj)
|
|
1273
1296
|
{
|
|
1274
1297
|
VALUE tmp;
|
|
@@ -1407,7 +1430,16 @@ start:
|
|
|
1407
1430
|
break;
|
|
1408
1431
|
case T_STRING:
|
|
1409
1432
|
if (klass != rb_cString) goto general;
|
|
1410
|
-
|
|
1433
|
+
|
|
1434
|
+
if (RB_LIKELY(valid_json_string_p(obj))) {
|
|
1435
|
+
raw_generate_json_string(buffer, data, obj);
|
|
1436
|
+
} else if (as_json_called) {
|
|
1437
|
+
raise_generator_error(obj, "source sequence is illegal/malformed utf-8");
|
|
1438
|
+
} else {
|
|
1439
|
+
obj = ensure_valid_encoding(data, obj, false, false);
|
|
1440
|
+
as_json_called = true;
|
|
1441
|
+
goto start;
|
|
1442
|
+
}
|
|
1411
1443
|
break;
|
|
1412
1444
|
case T_SYMBOL:
|
|
1413
1445
|
generate_json_symbol(buffer, data, obj);
|
|
@@ -1443,16 +1475,14 @@ static VALUE generate_json_try(VALUE d)
|
|
|
1443
1475
|
|
|
1444
1476
|
data->func(data->buffer, data, data->obj);
|
|
1445
1477
|
|
|
1446
|
-
return
|
|
1478
|
+
return fbuffer_finalize(data->buffer);
|
|
1447
1479
|
}
|
|
1448
1480
|
|
|
1449
|
-
static VALUE
|
|
1481
|
+
static VALUE generate_json_ensure(VALUE d)
|
|
1450
1482
|
{
|
|
1451
1483
|
struct generate_json_data *data = (struct generate_json_data *)d;
|
|
1452
1484
|
fbuffer_free(data->buffer);
|
|
1453
1485
|
|
|
1454
|
-
rb_exc_raise(exc);
|
|
1455
|
-
|
|
1456
1486
|
return Qundef;
|
|
1457
1487
|
}
|
|
1458
1488
|
|
|
@@ -1473,9 +1503,7 @@ static VALUE cState_partial_generate(VALUE self, VALUE obj, generator_func func,
|
|
|
1473
1503
|
.obj = obj,
|
|
1474
1504
|
.func = func
|
|
1475
1505
|
};
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
return fbuffer_finalize(&buffer);
|
|
1506
|
+
return rb_ensure(generate_json_try, (VALUE)&data, generate_json_ensure, (VALUE)&data);
|
|
1479
1507
|
}
|
|
1480
1508
|
|
|
1481
1509
|
/* call-seq:
|
|
@@ -1521,9 +1549,7 @@ static VALUE cState_generate_new(int argc, VALUE *argv, VALUE self)
|
|
|
1521
1549
|
.obj = obj,
|
|
1522
1550
|
.func = generate_json
|
|
1523
1551
|
};
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
return fbuffer_finalize(&buffer);
|
|
1552
|
+
return rb_ensure(generate_json_try, (VALUE)&data, generate_json_ensure, (VALUE)&data);
|
|
1527
1553
|
}
|
|
1528
1554
|
|
|
1529
1555
|
static VALUE cState_initialize(int argc, VALUE *argv, VALUE self)
|
|
@@ -2029,9 +2055,7 @@ static VALUE cState_m_generate(VALUE klass, VALUE obj, VALUE opts, VALUE io)
|
|
|
2029
2055
|
.obj = obj,
|
|
2030
2056
|
.func = generate_json,
|
|
2031
2057
|
};
|
|
2032
|
-
|
|
2033
|
-
|
|
2034
|
-
return fbuffer_finalize(&buffer);
|
|
2058
|
+
return rb_ensure(generate_json_try, (VALUE)&data, generate_json_ensure, (VALUE)&data);
|
|
2035
2059
|
}
|
|
2036
2060
|
|
|
2037
2061
|
/*
|
data/ext/json/ext/json.h
ADDED
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
#ifndef _JSON_H_
|
|
2
|
+
#define _JSON_H_
|
|
3
|
+
|
|
4
|
+
#include "ruby.h"
|
|
5
|
+
#include "ruby/encoding.h"
|
|
6
|
+
#include <stdint.h>
|
|
7
|
+
|
|
8
|
+
#if defined(RUBY_DEBUG) && RUBY_DEBUG
|
|
9
|
+
# define JSON_ASSERT RUBY_ASSERT
|
|
10
|
+
#else
|
|
11
|
+
# ifdef JSON_DEBUG
|
|
12
|
+
# include <assert.h>
|
|
13
|
+
# define JSON_ASSERT(x) assert(x)
|
|
14
|
+
# else
|
|
15
|
+
# define JSON_ASSERT(x)
|
|
16
|
+
# endif
|
|
17
|
+
#endif
|
|
18
|
+
|
|
19
|
+
/* shims */
|
|
20
|
+
|
|
21
|
+
#if SIZEOF_UINT64_T == SIZEOF_LONG_LONG
|
|
22
|
+
# define INT64T2NUM(x) LL2NUM(x)
|
|
23
|
+
# define UINT64T2NUM(x) ULL2NUM(x)
|
|
24
|
+
#elif SIZEOF_UINT64_T == SIZEOF_LONG
|
|
25
|
+
# define INT64T2NUM(x) LONG2NUM(x)
|
|
26
|
+
# define UINT64T2NUM(x) ULONG2NUM(x)
|
|
27
|
+
#else
|
|
28
|
+
# error No uint64_t conversion
|
|
29
|
+
#endif
|
|
30
|
+
|
|
31
|
+
/* This is the fallback definition from Ruby 3.4 */
|
|
32
|
+
#ifndef RBIMPL_STDBOOL_H
|
|
33
|
+
#if defined(__cplusplus)
|
|
34
|
+
# if defined(HAVE_STDBOOL_H) && (__cplusplus >= 201103L)
|
|
35
|
+
# include <cstdbool>
|
|
36
|
+
# endif
|
|
37
|
+
#elif defined(HAVE_STDBOOL_H)
|
|
38
|
+
# include <stdbool.h>
|
|
39
|
+
#elif !defined(HAVE__BOOL)
|
|
40
|
+
typedef unsigned char _Bool;
|
|
41
|
+
# define bool _Bool
|
|
42
|
+
# define true ((_Bool)+1)
|
|
43
|
+
# define false ((_Bool)+0)
|
|
44
|
+
# define __bool_true_false_are_defined
|
|
45
|
+
#endif
|
|
46
|
+
#endif
|
|
47
|
+
|
|
48
|
+
#ifndef NORETURN
|
|
49
|
+
#define NORETURN(x) x
|
|
50
|
+
#endif
|
|
51
|
+
|
|
52
|
+
#ifndef NOINLINE
|
|
53
|
+
#if defined(__has_attribute) && __has_attribute(noinline)
|
|
54
|
+
#define NOINLINE(x) __attribute__((noinline)) x
|
|
55
|
+
#else
|
|
56
|
+
#define NOINLINE(x) x
|
|
57
|
+
#endif
|
|
58
|
+
#endif
|
|
59
|
+
|
|
60
|
+
#ifndef ALWAYS_INLINE
|
|
61
|
+
#if defined(__has_attribute) && __has_attribute(always_inline)
|
|
62
|
+
#define ALWAYS_INLINE(x) inline __attribute__((always_inline)) x
|
|
63
|
+
#else
|
|
64
|
+
#define ALWAYS_INLINE(x) inline x
|
|
65
|
+
#endif
|
|
66
|
+
#endif
|
|
67
|
+
|
|
68
|
+
#ifndef RB_UNLIKELY
|
|
69
|
+
#define RB_UNLIKELY(expr) expr
|
|
70
|
+
#endif
|
|
71
|
+
|
|
72
|
+
#ifndef RB_LIKELY
|
|
73
|
+
#define RB_LIKELY(expr) expr
|
|
74
|
+
#endif
|
|
75
|
+
|
|
76
|
+
#ifndef MAYBE_UNUSED
|
|
77
|
+
# define MAYBE_UNUSED(x) x
|
|
78
|
+
#endif
|
|
79
|
+
|
|
80
|
+
#ifdef RUBY_DEBUG
|
|
81
|
+
#ifndef JSON_DEBUG
|
|
82
|
+
#define JSON_DEBUG RUBY_DEBUG
|
|
83
|
+
#endif
|
|
84
|
+
#endif
|
|
85
|
+
|
|
86
|
+
#if defined(__BYTE_ORDER__) && __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ && INTPTR_MAX == INT64_MAX
|
|
87
|
+
#define JSON_CPU_LITTLE_ENDIAN_64BITS 1
|
|
88
|
+
#else
|
|
89
|
+
#define JSON_CPU_LITTLE_ENDIAN_64BITS 0
|
|
90
|
+
#endif
|
|
91
|
+
|
|
92
|
+
#endif // _JSON_H_
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
require 'mkmf'
|
|
3
3
|
|
|
4
|
+
$defs << "-DJSON_DEBUG" if ENV["JSON_DEBUG"]
|
|
4
5
|
have_func("rb_enc_interned_str", "ruby/encoding.h") # RUBY_VERSION >= 3.0
|
|
6
|
+
have_func("rb_str_to_interned_str", "ruby.h") # RUBY_VERSION >= 3.0
|
|
5
7
|
have_func("rb_hash_new_capa", "ruby.h") # RUBY_VERSION >= 3.2
|
|
6
8
|
have_func("rb_hash_bulk_insert", "ruby.h") # Missing on TruffleRuby
|
|
7
9
|
have_func("strnlen", "string.h") # Missing on Solaris 10
|