oj 3.16.3 → 3.17.4

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.
Files changed (134) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +99 -0
  3. data/README.md +0 -16
  4. data/ext/oj/compat.c +3 -3
  5. data/ext/oj/custom.c +17 -11
  6. data/ext/oj/dump.c +619 -88
  7. data/ext/oj/dump.h +9 -2
  8. data/ext/oj/dump_compat.c +16 -9
  9. data/ext/oj/dump_leaf.c +1 -1
  10. data/ext/oj/dump_object.c +52 -27
  11. data/ext/oj/dump_strict.c +20 -14
  12. data/ext/oj/extconf.rb +11 -9
  13. data/ext/oj/fast.c +90 -65
  14. data/ext/oj/intern.c +1 -1
  15. data/ext/oj/mimic_json.c +14 -8
  16. data/ext/oj/object.c +18 -12
  17. data/ext/oj/oj.c +426 -11
  18. data/ext/oj/oj.h +60 -52
  19. data/ext/oj/parse.c +252 -48
  20. data/ext/oj/parse.h +0 -1
  21. data/ext/oj/parser.c +179 -47
  22. data/ext/oj/parser.h +4 -2
  23. data/ext/oj/rails.c +57 -36
  24. data/ext/oj/reader.c +17 -5
  25. data/ext/oj/rxclass.c +17 -1
  26. data/ext/oj/rxclass.h +2 -1
  27. data/ext/oj/safe.c +230 -0
  28. data/ext/oj/safe.h +79 -0
  29. data/ext/oj/saj.c +55 -20
  30. data/ext/oj/scp.c +3 -6
  31. data/ext/oj/simd.h +219 -0
  32. data/ext/oj/sparse.c +15 -3
  33. data/ext/oj/stream_writer.c +19 -14
  34. data/ext/oj/strict.c +2 -4
  35. data/ext/oj/string_writer.c +26 -14
  36. data/ext/oj/usual.c +37 -35
  37. data/ext/oj/wab.c +5 -4
  38. data/lib/oj/mimic.rb +1 -5
  39. data/lib/oj/schandler.rb +5 -4
  40. data/lib/oj/version.rb +1 -1
  41. data/pages/Encoding.md +1 -1
  42. metadata +22 -100
  43. data/test/_test_active.rb +0 -75
  44. data/test/_test_active_mimic.rb +0 -95
  45. data/test/_test_mimic_rails.rb +0 -123
  46. data/test/activerecord/result_test.rb +0 -31
  47. data/test/activesupport6/abstract_unit.rb +0 -44
  48. data/test/activesupport6/decoding_test.rb +0 -133
  49. data/test/activesupport6/encoding_test.rb +0 -507
  50. data/test/activesupport6/encoding_test_cases.rb +0 -98
  51. data/test/activesupport6/test_common.rb +0 -17
  52. data/test/activesupport6/test_helper.rb +0 -163
  53. data/test/activesupport6/time_zone_test_helpers.rb +0 -39
  54. data/test/activesupport7/abstract_unit.rb +0 -49
  55. data/test/activesupport7/decoding_test.rb +0 -125
  56. data/test/activesupport7/encoding_test.rb +0 -486
  57. data/test/activesupport7/encoding_test_cases.rb +0 -104
  58. data/test/activesupport7/time_zone_test_helpers.rb +0 -47
  59. data/test/files.rb +0 -29
  60. data/test/foo.rb +0 -14
  61. data/test/helper.rb +0 -39
  62. data/test/isolated/shared.rb +0 -309
  63. data/test/isolated/test_mimic_after.rb +0 -13
  64. data/test/isolated/test_mimic_alone.rb +0 -12
  65. data/test/isolated/test_mimic_as_json.rb +0 -45
  66. data/test/isolated/test_mimic_before.rb +0 -13
  67. data/test/isolated/test_mimic_define.rb +0 -28
  68. data/test/isolated/test_mimic_rails_after.rb +0 -22
  69. data/test/isolated/test_mimic_rails_before.rb +0 -21
  70. data/test/isolated/test_mimic_redefine.rb +0 -15
  71. data/test/json_gem/json_addition_test.rb +0 -216
  72. data/test/json_gem/json_common_interface_test.rb +0 -155
  73. data/test/json_gem/json_encoding_test.rb +0 -107
  74. data/test/json_gem/json_ext_parser_test.rb +0 -21
  75. data/test/json_gem/json_fixtures_test.rb +0 -36
  76. data/test/json_gem/json_generator_test.rb +0 -413
  77. data/test/json_gem/json_generic_object_test.rb +0 -90
  78. data/test/json_gem/json_parser_test.rb +0 -477
  79. data/test/json_gem/json_string_matching_test.rb +0 -42
  80. data/test/json_gem/test_helper.rb +0 -30
  81. data/test/mem.rb +0 -34
  82. data/test/perf.rb +0 -102
  83. data/test/perf_compat.rb +0 -128
  84. data/test/perf_dump.rb +0 -50
  85. data/test/perf_fast.rb +0 -162
  86. data/test/perf_file.rb +0 -62
  87. data/test/perf_object.rb +0 -134
  88. data/test/perf_once.rb +0 -59
  89. data/test/perf_parser.rb +0 -183
  90. data/test/perf_saj.rb +0 -101
  91. data/test/perf_scp.rb +0 -140
  92. data/test/perf_simple.rb +0 -289
  93. data/test/perf_strict.rb +0 -137
  94. data/test/perf_wab.rb +0 -129
  95. data/test/prec.rb +0 -23
  96. data/test/sample/change.rb +0 -13
  97. data/test/sample/dir.rb +0 -18
  98. data/test/sample/doc.rb +0 -35
  99. data/test/sample/file.rb +0 -47
  100. data/test/sample/group.rb +0 -15
  101. data/test/sample/hasprops.rb +0 -15
  102. data/test/sample/layer.rb +0 -11
  103. data/test/sample/line.rb +0 -20
  104. data/test/sample/oval.rb +0 -10
  105. data/test/sample/rect.rb +0 -9
  106. data/test/sample/shape.rb +0 -34
  107. data/test/sample/text.rb +0 -19
  108. data/test/sample.rb +0 -54
  109. data/test/sample_json.rb +0 -37
  110. data/test/test_compat.rb +0 -567
  111. data/test/test_custom.rb +0 -555
  112. data/test/test_debian.rb +0 -50
  113. data/test/test_fast.rb +0 -526
  114. data/test/test_file.rb +0 -250
  115. data/test/test_gc.rb +0 -60
  116. data/test/test_generate.rb +0 -21
  117. data/test/test_hash.rb +0 -39
  118. data/test/test_integer_range.rb +0 -72
  119. data/test/test_null.rb +0 -376
  120. data/test/test_object.rb +0 -1030
  121. data/test/test_parser.rb +0 -11
  122. data/test/test_parser_debug.rb +0 -27
  123. data/test/test_parser_saj.rb +0 -337
  124. data/test/test_parser_usual.rb +0 -227
  125. data/test/test_rails.rb +0 -35
  126. data/test/test_saj.rb +0 -188
  127. data/test/test_scp.rb +0 -431
  128. data/test/test_strict.rb +0 -441
  129. data/test/test_various.rb +0 -801
  130. data/test/test_wab.rb +0 -311
  131. data/test/test_writer.rb +0 -380
  132. data/test/tests.rb +0 -33
  133. data/test/tests_mimic.rb +0 -23
  134. data/test/tests_mimic_addition.rb +0 -16
data/ext/oj/saj.c CHANGED
@@ -82,7 +82,14 @@ inline static void next_non_white(ParseInfo pi) {
82
82
  case '\f':
83
83
  case '\n':
84
84
  case '\r': break;
85
- case '/': skip_comment(pi); break;
85
+ case '/':
86
+ skip_comment(pi);
87
+ /* A comment that is not terminated by a newline ends on the null
88
+ * terminator. Stop here so the loop does not step past it. */
89
+ if ('\0' == *pi->s) {
90
+ return;
91
+ }
92
+ break;
86
93
  default: return;
87
94
  }
88
95
  }
@@ -94,8 +101,7 @@ inline static void call_add_value(VALUE handler, VALUE value, const char *key) {
94
101
  if (0 == key) {
95
102
  k = Qnil;
96
103
  } else {
97
- k = rb_str_new2(key);
98
- k = oj_encode(k);
104
+ k = rb_utf8_str_new_cstr(key);
99
105
  }
100
106
  rb_funcall(handler, oj_add_value_id, 2, value, k);
101
107
  }
@@ -106,8 +112,7 @@ inline static void call_no_value(VALUE handler, ID method, const char *key) {
106
112
  if (0 == key) {
107
113
  k = Qnil;
108
114
  } else {
109
- k = rb_str_new2(key);
110
- k = oj_encode(k);
115
+ k = rb_utf8_str_new_cstr(key);
111
116
  }
112
117
  rb_funcall(handler, method, 1, k);
113
118
  }
@@ -120,14 +125,15 @@ static void skip_comment(ParseInfo pi) {
120
125
  if ('*' == *pi->s && '/' == *(pi->s + 1)) {
121
126
  pi->s++;
122
127
  return;
123
- } else if ('\0' == *pi->s) {
124
- if (pi->has_error) {
125
- call_error("comment not terminated", pi, __FILE__, __LINE__);
126
- } else {
127
- raise_error("comment not terminated", pi->str, pi->s);
128
- }
129
128
  }
130
129
  }
130
+ /* The loop only ends on the null terminator so the comment was never
131
+ * closed. */
132
+ if (pi->has_error) {
133
+ call_error("comment not terminated", pi, __FILE__, __LINE__);
134
+ } else {
135
+ raise_error("comment not terminated", pi->str, pi->s);
136
+ }
131
137
  } else if ('/' == *pi->s) {
132
138
  for (; 1; pi->s++) {
133
139
  switch (*pi->s) {
@@ -257,9 +263,8 @@ static void read_str(ParseInfo pi, const char *key) {
257
263
 
258
264
  text = read_quoted_value(pi);
259
265
  if (pi->has_add_value) {
260
- VALUE s = rb_str_new2(text);
266
+ VALUE s = rb_utf8_str_new_cstr(text);
261
267
 
262
- s = oj_encode(s);
263
268
  call_add_value(pi->handler, s, key);
264
269
  }
265
270
  }
@@ -578,7 +583,7 @@ static void saj_parse(VALUE handler, char *json) {
578
583
  /* initialize parse info */
579
584
  pi.str = json;
580
585
  pi.s = json;
581
- #if IS_WINDOWS
586
+ #if IS_WINDOWS || !defined(HAVE_GETRLIMIT)
582
587
  pi.stack_min = (void *)((char *)&obj - (512L * 1024L)); /* assume a 1M stack and give half to ruby */
583
588
  #else
584
589
  {
@@ -587,7 +592,7 @@ static void saj_parse(VALUE handler, char *json) {
587
592
  if (0 == getrlimit(RLIMIT_STACK, &lim) && RLIM_INFINITY != lim.rlim_cur) {
588
593
  pi.stack_min = (void *)((char *)&obj - (lim.rlim_cur / 4 * 3)); /* let 3/4ths of the stack be used only */
589
594
  } else {
590
- pi.stack_min = 0; /* indicates not to check stack limit */
595
+ pi.stack_min = 0; /* indicates not to check stack limit */
591
596
  }
592
597
  }
593
598
  #endif
@@ -609,6 +614,19 @@ static void saj_parse(VALUE handler, char *json) {
609
614
  }
610
615
  }
611
616
 
617
+ struct _sajArgs {
618
+ VALUE handler;
619
+ char *json;
620
+ };
621
+
622
+ static VALUE protect_saj_parse(VALUE x) {
623
+ struct _sajArgs *args = (struct _sajArgs *)x;
624
+
625
+ saj_parse(args->handler, args->json);
626
+
627
+ return Qnil;
628
+ }
629
+
612
630
  /* call-seq: saj_parse(handler, io)
613
631
  *
614
632
  * Parses an IO stream or file containing an JSON document. Raises an exception
@@ -651,8 +669,16 @@ oj_saj_parse(int argc, VALUE *argv, VALUE self) {
651
669
  len = lseek(fd, 0, SEEK_END);
652
670
  lseek(fd, 0, SEEK_SET);
653
671
  json = OJ_R_ALLOC_N(char, len + 1);
654
- if (0 >= (cnt = read(fd, json, len)) || cnt != (ssize_t)len) {
655
- rb_raise(rb_eIOError, "failed to read from IO Object.");
672
+ {
673
+ size_t total = 0;
674
+
675
+ while (total < len) {
676
+ cnt = read(fd, json + total, len - total);
677
+ if (cnt <= 0) {
678
+ rb_raise(rb_eIOError, "failed to read from IO Object.");
679
+ }
680
+ total += cnt;
681
+ }
656
682
  }
657
683
  json[len] = '\0';
658
684
  #endif
@@ -665,8 +691,17 @@ oj_saj_parse(int argc, VALUE *argv, VALUE self) {
665
691
  rb_raise(rb_eArgError, "saj_parse() expected a String or IO Object.");
666
692
  }
667
693
  }
668
- saj_parse(*argv, json);
669
- OJ_R_FREE(json);
670
-
694
+ {
695
+ // saj_parse() raises on a malformed document so the json buffer has to
696
+ // be freed even when the parse does not return normally.
697
+ struct _sajArgs args = {*argv, json};
698
+ int ex = 0;
699
+
700
+ rb_protect(protect_saj_parse, (VALUE)&args, &ex);
701
+ OJ_R_FREE(json);
702
+ if (0 != ex) {
703
+ rb_jump_tag(ex);
704
+ }
705
+ }
671
706
  return Qnil;
672
707
  }
data/ext/oj/scp.c CHANGED
@@ -56,9 +56,8 @@ static void add_value(ParseInfo pi, VALUE val) {
56
56
  }
57
57
 
58
58
  static void add_cstr(ParseInfo pi, const char *str, size_t len, const char *orig) {
59
- volatile VALUE rstr = rb_str_new(str, len);
59
+ volatile VALUE rstr = rb_utf8_str_new(str, len);
60
60
 
61
- rstr = oj_encode(rstr);
62
61
  rb_funcall(pi->handler, oj_add_value_id, 1, rstr);
63
62
  }
64
63
 
@@ -87,9 +86,8 @@ static VALUE hash_key(ParseInfo pi, const char *key, size_t klen) {
87
86
  }
88
87
 
89
88
  static void hash_set_cstr(ParseInfo pi, Val kval, const char *str, size_t len, const char *orig) {
90
- volatile VALUE rstr = rb_str_new(str, len);
89
+ volatile VALUE rstr = rb_utf8_str_new(str, len);
91
90
 
92
- rstr = oj_encode(rstr);
93
91
  rb_funcall(pi->handler, oj_hash_set_id, 3, stack_peek(&pi->stack)->val, oj_calc_hash_key(pi, kval), rstr);
94
92
  }
95
93
 
@@ -107,9 +105,8 @@ static void hash_set_value(ParseInfo pi, Val kval, VALUE value) {
107
105
  }
108
106
 
109
107
  static void array_append_cstr(ParseInfo pi, const char *str, size_t len, const char *orig) {
110
- volatile VALUE rstr = rb_str_new(str, len);
108
+ volatile VALUE rstr = rb_utf8_str_new(str, len);
111
109
 
112
- rstr = oj_encode(rstr);
113
110
  rb_funcall(pi->handler, oj_array_append_id, 2, stack_peek(&pi->stack)->val, rstr);
114
111
  }
115
112
 
data/ext/oj/simd.h ADDED
@@ -0,0 +1,219 @@
1
+ #ifndef OJ_SIMD_H
2
+ #define OJ_SIMD_H
3
+
4
+ // SIMD architecture detection and configuration
5
+ // This header provides unified SIMD support across different CPU architectures
6
+ // with cross-platform runtime detection (Windows/Linux/Mac)
7
+
8
+ // SIMD implementation enum - used for runtime selection
9
+ typedef enum _simd_implementation { SIMD_NONE, SIMD_NEON, SIMD_SSE2, SIMD_SSE42 } SIMD_Implementation;
10
+
11
+ // Define in oj.c.
12
+ extern SIMD_Implementation SIMD_Impl;
13
+
14
+ // Runtime CPU detection function (implemented in oj.c)
15
+ SIMD_Implementation oj_get_simd_implementation(void);
16
+
17
+ // =============================================================================
18
+ // Compiler compatibility macros
19
+ // =============================================================================
20
+
21
+ // Branch prediction hints
22
+ #if defined(__GNUC__) || defined(__clang__)
23
+ #define OJ_LIKELY(x) __builtin_expect(!!(x), 1)
24
+ #define OJ_UNLIKELY(x) __builtin_expect(!!(x), 0)
25
+ #else
26
+ #define OJ_LIKELY(x) (x)
27
+ #define OJ_UNLIKELY(x) (x)
28
+ #endif
29
+
30
+ // Prefetch hints
31
+ #if defined(__GNUC__) || defined(__clang__)
32
+ #define OJ_PREFETCH(addr) __builtin_prefetch(addr, 0, 0)
33
+ #elif defined(_MSC_VER)
34
+ #include <intrin.h>
35
+ #define OJ_PREFETCH(addr) _mm_prefetch((const char *)(addr), _MM_HINT_T0)
36
+ #else
37
+ #define OJ_PREFETCH(addr) ((void)0)
38
+ #endif
39
+
40
+ // Count trailing zeros (for SSE2 mask scanning)
41
+ #if defined(__GNUC__) || defined(__clang__)
42
+ #define OJ_CTZ(x) __builtin_ctz(x)
43
+ #define OJ_CTZ64(x) __builtin_ctzll(x)
44
+ #elif defined(_MSC_VER)
45
+ #include <intrin.h>
46
+ static __inline int oj_ctz_msvc(unsigned int x) {
47
+ unsigned long index;
48
+ if (0 == x) {
49
+ return 32;
50
+ }
51
+ _BitScanForward(&index, x);
52
+ return (int)index;
53
+ }
54
+ static __inline int oj_ctz64_msvc(uint64_t x) {
55
+ unsigned long index;
56
+ if (_BitScanForward64(&index, x)) {
57
+ return (int)index;
58
+ }
59
+ return 64;
60
+ }
61
+ #define OJ_CTZ(x) oj_ctz_msvc(x)
62
+ #define OJ_CTZ64(x) oj_ctz64_msvc(x)
63
+ #else
64
+ // Fallback: naive implementation
65
+ static inline int oj_ctz_fallback(unsigned int x) {
66
+ int count = 0;
67
+ while ((x & 1) == 0 && count < 32) {
68
+ x >>= 1;
69
+ count++;
70
+ }
71
+ return count;
72
+ }
73
+
74
+ static inline int oj_ctz64_fallback(uint64_t x) {
75
+ int count = 0;
76
+ while ((x & 1) == 0 && count < 64) {
77
+ x >>= 1;
78
+ count++;
79
+ }
80
+ return count;
81
+ }
82
+ #define OJ_CTZ(x) oj_ctz_fallback(x)
83
+ #define OJ_CTZ64(x) oj_ctz64_fallback(x)
84
+ #endif
85
+
86
+ // =============================================================================
87
+ // x86/x86_64 SIMD detection
88
+ // =============================================================================
89
+ #if defined(__x86_64__) || defined(__i386__) || defined(_M_IX86) || defined(_M_X64)
90
+ #define HAVE_SIMD_X86 1
91
+
92
+ // Include appropriate SIMD headers
93
+ #if defined(_MSC_VER)
94
+ // MSVC: use intrin.h for all intrinsics
95
+ #include <intrin.h>
96
+ #define HAVE_SIMD_SSE4_2 1
97
+ #define HAVE_SIMD_SSE2 1
98
+ #elif defined(__GNUC__) || defined(__clang__)
99
+ // GCC/Clang: check for header availability and include them
100
+ // We include headers but use target attributes to enable instructions per-function
101
+ // Include cpuid.h for __get_cpuid fallback when __builtin_cpu_supports is unavailable
102
+ #if __has_include(<cpuid.h>)
103
+ #include <cpuid.h>
104
+ #endif
105
+ #if defined(__SSE4_2__) || defined(__SSE2__)
106
+ // If any SSE is enabled globally, x86intrin.h should be available
107
+ #include <x86intrin.h>
108
+ #define HAVE_SIMD_SSE4_2 1
109
+ #define HAVE_SIMD_SSE2 1
110
+ #else
111
+ // Try to include headers anyway for target attribute functions
112
+ #if __has_include(<x86intrin.h>)
113
+ #include <x86intrin.h>
114
+ #define HAVE_SIMD_SSE4_2 1
115
+ #define HAVE_SIMD_SSE2 1
116
+ #elif __has_include(<nmmintrin.h>)
117
+ #include <nmmintrin.h>
118
+ #define HAVE_SIMD_SSE4_2 1
119
+ #define HAVE_SIMD_SSE2 1
120
+ #elif __has_include(<emmintrin.h>)
121
+ #include <emmintrin.h>
122
+ #define HAVE_SIMD_SSE2 1
123
+ #endif
124
+ #endif
125
+ #endif
126
+
127
+ // Target attribute macros for function-level SIMD enabling
128
+ #if defined(__clang__) || defined(__GNUC__)
129
+ #define OJ_TARGET_SSE42 __attribute__((target("sse4.2")))
130
+ #define OJ_TARGET_SSE2 __attribute__((target("sse2")))
131
+ #else
132
+ // MSVC doesn't need target attributes - intrinsics are always available
133
+ #define OJ_TARGET_SSE42
134
+ #define OJ_TARGET_SSE2
135
+ #endif
136
+
137
+ #endif // x86/x86_64
138
+
139
+ // =============================================================================
140
+ // ARM NEON detection
141
+ // =============================================================================
142
+ #if defined(__ARM_NEON) || defined(__ARM_NEON__) || defined(__aarch64__) || defined(_M_ARM64)
143
+ #define HAVE_SIMD_NEON 1
144
+ #define SIMD_MINIMUM_THRESHOLD 6
145
+ #include <arm_neon.h>
146
+ #endif
147
+
148
+ // =============================================================================
149
+ // SIMD type string for debugging/logging
150
+ // =============================================================================
151
+ #if defined(HAVE_SIMD_SSE4_2) || defined(HAVE_SIMD_SSE2)
152
+ #define HAVE_SIMD_STRING_SCAN 1
153
+ #define SIMD_TYPE "x86 (runtime detected)"
154
+ #elif defined(HAVE_SIMD_NEON)
155
+ #define HAVE_SIMD_STRING_SCAN 1
156
+ #define SIMD_TYPE "NEON"
157
+ #else
158
+ #define SIMD_TYPE "none"
159
+ #endif
160
+
161
+ #if defined(HAVE_SIMD_SSE4_2)
162
+
163
+ #define SIMD_MINIMUM_THRESHOLD 6
164
+
165
+ extern void initialize_sse42(void);
166
+
167
+ static inline OJ_TARGET_SSE42 __m128i vector_lookup_sse42(__m128i input, __m128i *lookup_table, int tab_size) {
168
+ // Extract high 4 bits to determine which 16-byte chunk (0-15)
169
+ __m128i hi_index = _mm_and_si128(_mm_srli_epi32(input, 4), _mm_set1_epi8(0x0F));
170
+
171
+ // Extract low 4 bits for index within the chunk (0-15)
172
+ __m128i low_index = _mm_and_si128(input, _mm_set1_epi8(0x0F));
173
+
174
+ // Perform lookups in all 16 tables
175
+ __m128i results[16];
176
+ for (int i = 0; i < tab_size; i++) {
177
+ results[i] = _mm_shuffle_epi8(lookup_table[i], low_index);
178
+ }
179
+
180
+ // Create masks for each chunk and blend results
181
+ __m128i final_result = _mm_setzero_si128();
182
+
183
+ for (int i = 0; i < tab_size; i++) {
184
+ __m128i mask = _mm_cmpeq_epi8(hi_index, _mm_set1_epi8(i));
185
+ __m128i masked_result = _mm_and_si128(mask, results[i]);
186
+ final_result = _mm_or_si128(final_result, masked_result);
187
+ }
188
+
189
+ return final_result;
190
+ }
191
+
192
+ #endif
193
+
194
+ #ifndef __has_builtin
195
+ #define __has_builtin(x) 0
196
+ #endif
197
+
198
+ #if __has_builtin(__builtin_memcpy)
199
+ #define HAVE_FAST_MEMCPY 1
200
+
201
+ inline static void fast_memcpy16(void *dest, const void *src, size_t n) {
202
+ char *d = (char *)dest;
203
+ char *s = (char *)src;
204
+ if (n >= 8) {
205
+ __builtin_memcpy(d, s, 8);
206
+ __builtin_memcpy(d + n - 8, s + n - 8, 8);
207
+ } else if (n >= 4) {
208
+ __builtin_memcpy(d, s, 4);
209
+ __builtin_memcpy(d + n - 4, s + n - 4, 4);
210
+ } else if (n >= 2) {
211
+ __builtin_memcpy(d, s, 2);
212
+ __builtin_memcpy(d + n - 2, s + n - 2, 2);
213
+ } else if (n >= 1) {
214
+ *d = *s;
215
+ }
216
+ }
217
+ #endif
218
+
219
+ #endif /* OJ_SIMD_H */
data/ext/oj/sparse.c CHANGED
@@ -399,6 +399,7 @@ static void read_num(ParseInfo pi) {
399
399
  char c;
400
400
 
401
401
  reader_protect(&pi->rd);
402
+ ni.pi = pi;
402
403
  ni.i = 0;
403
404
  ni.num = 0;
404
405
  ni.div = 1;
@@ -415,7 +416,7 @@ static void read_num(ParseInfo pi) {
415
416
  ni.bigdec_load = pi->options.compat_bigdec;
416
417
  } else {
417
418
  ni.no_big = (FloatDec == pi->options.bigdec_load || FastDec == pi->options.bigdec_load ||
418
- RubyDec == pi->options.bigdec_load);
419
+ RubyDec == pi->options.bigdec_load);
419
420
  ni.bigdec_load = pi->options.bigdec_load;
420
421
  }
421
422
 
@@ -549,6 +550,7 @@ static void read_nan(ParseInfo pi) {
549
550
  struct _numInfo ni;
550
551
  char c;
551
552
 
553
+ ni.pi = pi;
552
554
  ni.str = pi->rd.str;
553
555
  ni.i = 0;
554
556
  ni.num = 0;
@@ -565,7 +567,7 @@ static void read_nan(ParseInfo pi) {
565
567
  ni.bigdec_load = pi->options.compat_bigdec;
566
568
  } else {
567
569
  ni.no_big = (FloatDec == pi->options.bigdec_load || FastDec == pi->options.bigdec_load ||
568
- RubyDec == pi->options.bigdec_load);
570
+ RubyDec == pi->options.bigdec_load);
569
571
  ni.bigdec_load = pi->options.bigdec_load;
570
572
  }
571
573
 
@@ -745,6 +747,7 @@ void oj_sparse2(ParseInfo pi) {
745
747
  oj_set_error_at(pi, oj_parse_error_class, __FILE__, __LINE__, "expected NaN");
746
748
  return;
747
749
  }
750
+ ni.pi = pi;
748
751
  ni.str = pi->rd.str;
749
752
  ni.i = 0;
750
753
  ni.num = 0;
@@ -761,7 +764,7 @@ void oj_sparse2(ParseInfo pi) {
761
764
  ni.bigdec_load = pi->options.compat_bigdec;
762
765
  } else {
763
766
  ni.no_big = (FloatDec == pi->options.bigdec_load || FastDec == pi->options.bigdec_load ||
764
- RubyDec == pi->options.bigdec_load);
767
+ RubyDec == pi->options.bigdec_load);
765
768
  ni.bigdec_load = pi->options.bigdec_load;
766
769
  }
767
770
  add_num_value(pi, &ni);
@@ -904,6 +907,15 @@ CLEANUP:
904
907
  oj_circ_array_free(pi->circ_array);
905
908
  }
906
909
  stack_cleanup(&pi->stack);
910
+ // A :match_string option builds a chain of regexps for this call that has to
911
+ // be freed. Without the option the member aliases the chain owned by the
912
+ // defaults and must be left alone.
913
+ if (pi->options.str_rx.head != oj_default_options.str_rx.head) {
914
+ oj_rxclass_cleanup(&pi->options.str_rx);
915
+ pi->options.str_rx.head = NULL;
916
+ pi->options.str_rx.tail = NULL;
917
+ }
918
+ oj_free_call_options(&pi->options);
907
919
  if (0 != fd) {
908
920
  #ifdef _WIN32
909
921
  rb_w32_close(fd);
@@ -16,15 +16,29 @@ static void stream_writer_free(void *ptr) {
16
16
  return;
17
17
  }
18
18
  sw = (StreamWriter)ptr;
19
+ oj_options_release(&sw->sw.opts);
19
20
  OJ_R_FREE(sw->sw.out.buf);
20
21
  OJ_R_FREE(sw->sw.types);
21
22
  OJ_R_FREE(ptr);
22
23
  }
23
24
 
25
+ static void stream_writer_mark(void *ptr) {
26
+ if (NULL != ptr) {
27
+ StreamWriter sw = (StreamWriter)ptr;
28
+
29
+ oj_options_mark(&sw->sw.opts);
30
+ // The writer can be the only reference to the stream as in
31
+ // Oj::StreamWriter.new(StringIO.new) so it must be marked.
32
+ if (Qnil != sw->stream) {
33
+ rb_gc_mark(sw->stream);
34
+ }
35
+ }
36
+ }
37
+
24
38
  static const rb_data_type_t oj_stream_writer_type = {
25
39
  "Oj/stream_writer",
26
40
  {
27
- NULL,
41
+ stream_writer_mark,
28
42
  stream_writer_free,
29
43
  NULL,
30
44
  },
@@ -42,22 +56,12 @@ static void stream_writer_write(StreamWriter sw) {
42
56
 
43
57
  switch (sw->type) {
44
58
  case STRING_IO:
45
- case STREAM_IO: {
46
- volatile VALUE rs = rb_str_new(sw->sw.out.buf, size);
47
-
48
- // Oddly enough, when pushing ASCII characters with UTF-8 encoding or
49
- // even ASCII-8BIT does not change the output encoding. Pushing any
50
- // non-ASCII no matter what the encoding changes the output encoding
51
- // to ASCII-8BIT if it the string is not forced to UTF-8 here.
52
- rs = oj_encode(rs);
59
+ case STREAM_IO:
60
+ case FILE_IO: {
61
+ volatile VALUE rs = rb_utf8_str_new(sw->sw.out.buf, size);
53
62
  rb_funcall(sw->stream, oj_write_id, 1, rs);
54
63
  break;
55
64
  }
56
- case FILE_IO:
57
- if (size != write(sw->fd, sw->sw.out.buf, size)) {
58
- rb_raise(rb_eIOError, "Write failed. [_%d_:%s]\n", errno, strerror(errno));
59
- }
60
- break;
61
65
  default: rb_raise(rb_eArgError, "expected an IO Object.");
62
66
  }
63
67
  stream_writer_reset_buf(sw);
@@ -126,6 +130,7 @@ static VALUE stream_writer_new(int argc, VALUE *argv, VALUE self) {
126
130
  oj_str_writer_init(&sw->sw, 4096);
127
131
  sw->flush_limit = 0;
128
132
  }
133
+ oj_options_take_ownership(&sw->sw.opts);
129
134
  sw->sw.out.indent = sw->sw.opts.indent;
130
135
  sw->stream = stream;
131
136
  sw->type = type;
data/ext/oj/strict.c CHANGED
@@ -19,8 +19,7 @@ VALUE oj_cstr_to_value(const char *str, size_t len, size_t cache_str) {
19
19
  if (len < cache_str) {
20
20
  rstr = oj_str_intern(str, len);
21
21
  } else {
22
- rstr = rb_str_new(str, len);
23
- rstr = oj_encode(rstr);
22
+ rstr = rb_utf8_str_new(str, len);
24
23
  }
25
24
  return rstr;
26
25
  }
@@ -35,8 +34,7 @@ VALUE oj_calc_hash_key(ParseInfo pi, Val parent) {
35
34
  if (Yes == pi->options.sym_key) {
36
35
  rkey = ID2SYM(rb_intern3(parent->key, parent->klen, oj_utf8_encoding));
37
36
  } else {
38
- rkey = rb_str_new(parent->key, parent->klen);
39
- rkey = oj_encode(rkey);
37
+ rkey = rb_utf8_str_new(parent->key, parent->klen);
40
38
  OBJ_FREEZE(rkey); // frozen when used as a Hash key anyway
41
39
  }
42
40
  return rkey;
@@ -42,12 +42,18 @@ static void maybe_comma(StrWriter sw) {
42
42
 
43
43
  // Used by stream writer also.
44
44
  void oj_str_writer_init(StrWriter sw, int buf_size) {
45
- sw->opts = oj_default_options;
46
- sw->depth = 0;
47
- sw->types = OJ_R_ALLOC_N(char, 256);
48
- sw->types_end = sw->types + 256;
49
- *sw->types = '\0';
50
- sw->keyWritten = 0;
45
+ sw->opts = oj_default_options;
46
+ // Detach from the match_string regexps owned by the defaults before the
47
+ // options are parsed. A :match_string option then builds a chain of its own
48
+ // that is freed with the writer instead of being appended to the chain the
49
+ // defaults are using.
50
+ sw->opts.str_rx.head = NULL;
51
+ sw->opts.str_rx.tail = NULL;
52
+ sw->depth = 0;
53
+ sw->types = OJ_R_ALLOC_N(char, 256);
54
+ sw->types_end = sw->types + 256;
55
+ *sw->types = '\0';
56
+ sw->keyWritten = 0;
51
57
 
52
58
  if (0 == buf_size) {
53
59
  buf_size = 4096;
@@ -234,15 +240,22 @@ static void string_writer_free(void *ptr) {
234
240
  sw = (StrWriter)ptr;
235
241
 
236
242
  oj_out_free(&sw->out);
243
+ oj_options_release(&sw->opts);
237
244
 
238
245
  OJ_R_FREE(sw->types);
239
246
  OJ_R_FREE(ptr);
240
247
  }
241
248
 
249
+ static void string_writer_mark(void *ptr) {
250
+ if (NULL != ptr) {
251
+ oj_options_mark(&((StrWriter)ptr)->opts);
252
+ }
253
+ }
254
+
242
255
  static const rb_data_type_t oj_string_writer_type = {
243
256
  "Oj/string_writer",
244
257
  {
245
- NULL,
258
+ string_writer_mark,
246
259
  string_writer_free,
247
260
  NULL,
248
261
  },
@@ -279,6 +292,7 @@ static VALUE str_writer_new(int argc, VALUE *argv, VALUE self) {
279
292
  if (1 == argc) {
280
293
  oj_parse_options(argv[0], &sw->opts);
281
294
  }
295
+ oj_options_take_ownership(&sw->opts);
282
296
  sw->out.argc = argc - 1;
283
297
  sw->out.argv = argv + 1;
284
298
  sw->out.indent = sw->opts.indent;
@@ -469,22 +483,20 @@ static VALUE str_writer_reset(VALUE self) {
469
483
  static VALUE str_writer_to_s(VALUE self) {
470
484
  StrWriter sw;
471
485
  TypedData_Get_Struct(self, struct _strWriter, &oj_string_writer_type, sw);
472
- VALUE rstr = rb_str_new(sw->out.buf, sw->out.cur - sw->out.buf);
473
-
474
- return oj_encode(rstr);
486
+ return rb_utf8_str_new(sw->out.buf, sw->out.cur - sw->out.buf);
475
487
  }
476
488
 
477
489
  /* Document-method: as_json
478
- * call-seq: as_json()
490
+ * call-seq: as_json(*)
479
491
  *
480
492
  * Returns the contents of the writer as a JSON element. If called from inside
481
493
  * an array or hash by Oj the raw buffer will be used othersize a more
482
494
  * inefficient parse of the contents and a return of the result is
483
- * completed. The parse uses the strict mode.
495
+ * completed. The parse uses the strict mode. Optional arguments are ignored.
484
496
  *
485
497
  * *return* [_Hash_|_Array_|_String_|_Integer_|_Float_|_True_|_False_|_nil|)
486
498
  */
487
- static VALUE str_writer_as_json(VALUE self) {
499
+ static VALUE str_writer_as_json(int argc, VALUE *argv, VALUE self) {
488
500
  if (string_writer_optimized) {
489
501
  return self;
490
502
  }
@@ -515,5 +527,5 @@ void oj_string_writer_init(void) {
515
527
  rb_define_method(oj_string_writer_class, "reset", str_writer_reset, 0);
516
528
  rb_define_method(oj_string_writer_class, "to_s", str_writer_to_s, 0);
517
529
  rb_define_method(oj_string_writer_class, "raw_json", str_writer_to_s, 0);
518
- rb_define_method(oj_string_writer_class, "as_json", str_writer_as_json, 0);
530
+ rb_define_method(oj_string_writer_class, "as_json", str_writer_as_json, -1);
519
531
  }