msgpack 1.4.2 → 1.7.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (95) hide show
  1. checksums.yaml +4 -4
  2. data/ChangeLog +85 -0
  3. data/README.md +52 -1
  4. data/ext/java/org/msgpack/jruby/Buffer.java +26 -19
  5. data/ext/java/org/msgpack/jruby/Decoder.java +29 -21
  6. data/ext/java/org/msgpack/jruby/Encoder.java +68 -30
  7. data/ext/java/org/msgpack/jruby/ExtensionRegistry.java +43 -64
  8. data/ext/java/org/msgpack/jruby/ExtensionValue.java +6 -9
  9. data/ext/java/org/msgpack/jruby/Factory.java +43 -42
  10. data/ext/java/org/msgpack/jruby/Packer.java +37 -40
  11. data/ext/java/org/msgpack/jruby/Unpacker.java +80 -73
  12. data/ext/msgpack/buffer.c +54 -74
  13. data/ext/msgpack/buffer.h +21 -18
  14. data/ext/msgpack/buffer_class.c +161 -52
  15. data/ext/msgpack/buffer_class.h +1 -0
  16. data/ext/msgpack/compat.h +0 -99
  17. data/ext/msgpack/extconf.rb +25 -46
  18. data/ext/msgpack/factory_class.c +143 -87
  19. data/ext/msgpack/packer.c +66 -43
  20. data/ext/msgpack/packer.h +25 -20
  21. data/ext/msgpack/packer_class.c +102 -130
  22. data/ext/msgpack/packer_class.h +11 -0
  23. data/ext/msgpack/packer_ext_registry.c +35 -40
  24. data/ext/msgpack/packer_ext_registry.h +41 -38
  25. data/ext/msgpack/rbinit.c +1 -1
  26. data/ext/msgpack/rmem.c +3 -4
  27. data/ext/msgpack/sysdep.h +5 -2
  28. data/ext/msgpack/unpacker.c +126 -108
  29. data/ext/msgpack/unpacker.h +16 -13
  30. data/ext/msgpack/unpacker_class.c +86 -126
  31. data/ext/msgpack/unpacker_class.h +11 -0
  32. data/ext/msgpack/unpacker_ext_registry.c +40 -28
  33. data/ext/msgpack/unpacker_ext_registry.h +21 -18
  34. data/lib/msgpack/bigint.rb +69 -0
  35. data/lib/msgpack/buffer.rb +9 -0
  36. data/lib/msgpack/factory.rb +140 -10
  37. data/lib/msgpack/packer.rb +10 -1
  38. data/lib/msgpack/symbol.rb +21 -4
  39. data/lib/msgpack/time.rb +1 -1
  40. data/lib/msgpack/unpacker.rb +14 -1
  41. data/lib/msgpack/version.rb +1 -1
  42. data/lib/msgpack.rb +6 -7
  43. data/msgpack.gemspec +8 -5
  44. metadata +37 -82
  45. data/.gitignore +0 -23
  46. data/.rubocop.yml +0 -36
  47. data/.travis.yml +0 -39
  48. data/Gemfile +0 -9
  49. data/Rakefile +0 -71
  50. data/appveyor.yml +0 -18
  51. data/bench/pack.rb +0 -23
  52. data/bench/pack_log.rb +0 -33
  53. data/bench/pack_log_long.rb +0 -65
  54. data/bench/pack_symbols.rb +0 -28
  55. data/bench/run.sh +0 -14
  56. data/bench/run_long.sh +0 -35
  57. data/bench/run_symbols.sh +0 -26
  58. data/bench/unpack.rb +0 -21
  59. data/bench/unpack_log.rb +0 -34
  60. data/bench/unpack_log_long.rb +0 -67
  61. data/doclib/msgpack/buffer.rb +0 -193
  62. data/doclib/msgpack/core_ext.rb +0 -101
  63. data/doclib/msgpack/error.rb +0 -19
  64. data/doclib/msgpack/extension_value.rb +0 -9
  65. data/doclib/msgpack/factory.rb +0 -101
  66. data/doclib/msgpack/packer.rb +0 -208
  67. data/doclib/msgpack/time.rb +0 -22
  68. data/doclib/msgpack/timestamp.rb +0 -44
  69. data/doclib/msgpack/unpacker.rb +0 -183
  70. data/doclib/msgpack.rb +0 -87
  71. data/msgpack.org.md +0 -46
  72. data/spec/cases.json +0 -1
  73. data/spec/cases.msg +0 -0
  74. data/spec/cases_compact.msg +0 -0
  75. data/spec/cases_spec.rb +0 -39
  76. data/spec/cruby/buffer_io_spec.rb +0 -255
  77. data/spec/cruby/buffer_packer.rb +0 -29
  78. data/spec/cruby/buffer_spec.rb +0 -575
  79. data/spec/cruby/buffer_unpacker.rb +0 -19
  80. data/spec/cruby/unpacker_spec.rb +0 -70
  81. data/spec/ext_value_spec.rb +0 -99
  82. data/spec/exttypes.rb +0 -51
  83. data/spec/factory_spec.rb +0 -367
  84. data/spec/format_spec.rb +0 -301
  85. data/spec/jruby/benchmarks/shootout_bm.rb +0 -73
  86. data/spec/jruby/benchmarks/symbolize_keys_bm.rb +0 -25
  87. data/spec/jruby/unpacker_spec.rb +0 -186
  88. data/spec/msgpack_spec.rb +0 -214
  89. data/spec/pack_spec.rb +0 -61
  90. data/spec/packer_spec.rb +0 -557
  91. data/spec/random_compat.rb +0 -24
  92. data/spec/spec_helper.rb +0 -55
  93. data/spec/timestamp_spec.rb +0 -121
  94. data/spec/unpack_spec.rb +0 -57
  95. data/spec/unpacker_spec.rb +0 -819
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 451ed743fe37bd3726f897f4daae9e7fd9b2cb065a34cf438ca786350060e0ec
4
- data.tar.gz: 87335c4b1d35f9ac88af648963b1fe60caa72059ec7e6c1b6b507027da9118d8
3
+ metadata.gz: 2f5b1af6b3a51f5ccc6bcf67c94c1fc6193b02fe01b123e2cfb06a6df9607116
4
+ data.tar.gz: cc057f24e1ffa4cdc3e331499eb04de4c2383b0657dcf0baeba08300fd20862e
5
5
  SHA512:
6
- metadata.gz: eb519049e9d8e4b36682e4b2c2625f10172745ce74ad1cfc8e6d93468b219071ed579bcfd36b7f0e1210eeb177197b435da2a028f46d2c71c73a3bcf2b53dcf8
7
- data.tar.gz: e8c4765ac836226f5a2ce68a62194f112fc77bcdd9744d39040ff2db5a2c27d876348fb6002110c00b122bccdff2d539223d82ff73ea0bcb4cf1a3d9fb62defe
6
+ metadata.gz: 3eb06321a534ca9b16e321cc4a71458532578dafe7967314a662223b1fbf4aa93449c98177fa982aa532ce3732ddda4a6d497704df0e9c874da07f378c73595c
7
+ data.tar.gz: 8e540755e3db9e21d7dfa4354854e8b0486f5a1bbf82c3994c6095022205f7873153d364df9310d8072c481de38ca2b4c3e088e4221c3451ceb9438312489419
data/ChangeLog CHANGED
@@ -1,3 +1,88 @@
1
+ 2023-07-18 1.7.2:
2
+
3
+ * Fix a potential GC bug when packing data using recursive extensions and buffers containing over 512KkiB of data (See #341).
4
+ * Fix a regression where feeding an empty string to an Unpacker would be considered like the end of the buffer.
5
+
6
+ 2023-05-19 1.7.1:
7
+
8
+ * Fix JRuby 9.4 compatibility.
9
+ * Fix compilation on older compilers (gcc 4.x).
10
+ * Fix an infinite recursion issue when registering a Symbol type with a `nil` packer.
11
+
12
+ 2023-03-29 1.7.0:
13
+
14
+ * Fix a possible double-free issue when GC triggers inside `_msgpack_rmem_alloc2`.
15
+ * `Unpacker#feed` now always directly read in provided strings instead of copying content in its buffer.
16
+ * `Unpacker#feed` is now an alias of `Unpacker#feed_reference`.
17
+ * Implement `Factory::Pool#unpacker` and `Factory::Pool#packer` to allow for more precise serialization.
18
+ * Require Ruby 2.5+.
19
+
20
+ 2023-03-03 1.6.1:
21
+
22
+ * Undefine `#clone` and `#dup` on `MessagePack::Buffer`, `MessagePack::Packer` and `MessagePack::Unpacker`.
23
+ These methods were never intended, and using them could cause leaks or crashes or worse.
24
+ * Fix a possible GC crash when GC trigger inside `MessagePack::Buffer.new` (#314).
25
+
26
+ 2022-09-30 1.6.0:
27
+
28
+ * Fix a potential use-after-free bug in Buffer_free when accessing a packer or unpacker buffer.
29
+ * `old-style-definition` compilation warnings.
30
+ * Restore zero-copy buffer feed when provided a Ruby string. This was accidentally broken in 1.5.4.
31
+ * Provide implementations for `ObjectSpace.memsize`. Message pack objects now properly report their size to Ruby.
32
+ * Fix an endianess bug on Windows platform.
33
+
34
+ 2022-08-23 1.5.6:
35
+
36
+ * No actual code change, just re-release the `java` version properly.
37
+
38
+ 2022-08-22 1.5.5:
39
+
40
+ * Fix a segfault when GC triggers inside a recursive extension.
41
+
42
+ 2022-07-25 1.5.4:
43
+
44
+ * Fix a segfault when deserializing empty symbol (`:""`).
45
+ * Improve compilation flags to not strip debug symbols.
46
+
47
+ 2022-05-30 version 1.5.3:
48
+
49
+ * Fix deduplication of empty strings when using the `freeze: true` option.
50
+ * Use `rb_hash_new_capa` when available (Ruby 3.2) for improved performance when parsing large hashes.
51
+
52
+ 2022-05-27 version 1.5.2:
53
+
54
+ * Fix bug about unpacking ext type objects with the recursive option
55
+
56
+ 2022-04-07 version 1.5.1:
57
+
58
+ * Fix bug about packing/unpacking ext type objects with the recursive option
59
+
60
+ 2022-04-06 version 1.5.0:
61
+
62
+ * Add recursive option on Factory#register_type to operate Packer/Unpacker manually
63
+ * Add oversized_integer_extension option on Factory#register_type to pack/unpack bigint using ext types
64
+ * Add Factory#pool method and Factory::Pool class to provide pooled Packer and Unpacker instances
65
+
66
+ 2022-02-15 version 1.4.5:
67
+
68
+ * Fix to create UTF-8 Symbol keys when symbolize_keys: true
69
+ * Fix to assume Symbols as US-ASCII or UTF-8
70
+ * Optimize Packer/Unpacker initialization
71
+ * Optimize extension class lookup
72
+ * Rename Packer#clear as Packer#reset (#clear is still available as an alias)
73
+
74
+ 2022-01-22 version 1.4.4:
75
+
76
+ * Specify the build option --platform=8 for older Java platforms
77
+
78
+ 2022-01-20 version 1.4.3:
79
+
80
+ * Optimize serialization/deserialization of Symbols
81
+ * Support registering ext types for objects of subclasses of primitive types (like Hash)
82
+ * Add optimized_symbols_parsing option to Factory#register_type on MRI implementation
83
+ * Optimize to deduplicate Hash keys on JRuby
84
+ * Support JRuby 9.3 (and drop 9.1)
85
+
1
86
  2021-02-01 version 1.4.2:
2
87
 
3
88
  * Add the required Ruby version (>= 2.4) to avoid compilation errors on older Ruby runtimes
data/README.md CHANGED
@@ -40,7 +40,7 @@ or build msgpack-ruby and install:
40
40
  MessagePack for Ruby should run on x86, ARM, PowerPC, SPARC and other CPU architectures.
41
41
 
42
42
  And it works with MRI (CRuby) and Rubinius.
43
- Patches to improve portability is highly welcomed.
43
+ Patches to improve portability are highly welcomed.
44
44
 
45
45
 
46
46
  ## Serializing objects
@@ -51,6 +51,7 @@ Use `MessagePack.pack` or `to_msgpack`:
51
51
  require 'msgpack'
52
52
  msg = MessagePack.pack(obj) # or
53
53
  msg = obj.to_msgpack
54
+ File.binwrite('mydata.msgpack', msg)
54
55
  ```
55
56
 
56
57
  ### Streaming serialization
@@ -71,6 +72,7 @@ Use `MessagePack.unpack`:
71
72
 
72
73
  ```ruby
73
74
  require 'msgpack'
75
+ msg = File.binread('mydata.msgpack')
74
76
  obj = MessagePack.unpack(msg)
75
77
  ```
76
78
 
@@ -187,6 +189,55 @@ MessagePack::DefaultFactory.register_type(0x03, MyClass3)
187
189
  MessagePack.unpack(data_with_ext_typeid_03) #=> MyClass3 instance
188
190
  ```
189
191
 
192
+ Alternatively, extension types can call the packer or unpacker recursively to generate the extension data:
193
+
194
+ ```ruby
195
+ Point = Struct.new(:x, :y)
196
+ factory = MessagePack::Factory.new
197
+ factory.register_type(
198
+ 0x01,
199
+ Point,
200
+ packer: ->(point, packer) {
201
+ packer.write(point.x)
202
+ packer.write(point.y)
203
+ },
204
+ unpacker: ->(unpacker) {
205
+ x = unpacker.read
206
+ y = unpacker.read
207
+ Point.new(x, y)
208
+ },
209
+ recursive: true,
210
+ )
211
+ factory.load(factory.dump(Point.new(12, 34))) # => #<struct Point x=12, y=34>
212
+ ```
213
+
214
+ ## Pooling
215
+
216
+ Creating `Packer` and `Unpacker` objects is expensive. For best performance it is preferable to re-use these objects.
217
+
218
+ `MessagePack::Factory#pool` makes that easier:
219
+
220
+ ```ruby
221
+ factory = MessagePack::Factory.new
222
+ factory.register_type(
223
+ 0x01,
224
+ Point,
225
+ packer: ->(point, packer) {
226
+ packer.write(point.x)
227
+ packer.write(point.y)
228
+ },
229
+ unpacker: ->(unpacker) {
230
+ x = unpacker.read
231
+ y = unpacker.read
232
+ Point.new(x, y)
233
+ },
234
+ recursive: true,
235
+ )
236
+ pool = factory.pool(5) # The pool size should match the number of threads expected to use the factory concurrently.
237
+
238
+ pool.load(pool.dump(Point.new(12, 34))) # => #<struct Point x=12, y=34>
239
+ ```
240
+
190
241
  ## Buffer API
191
242
 
192
243
  MessagePack for Ruby provides a buffer API so that you can read or write data by hand, not via Packer or Unpacker API.
@@ -21,10 +21,11 @@ import org.jcodings.Encoding;
21
21
 
22
22
  @JRubyClass(name="MessagePack::Buffer")
23
23
  public class Buffer extends RubyObject {
24
- private IRubyObject io;
25
- private ByteBuffer buffer;
24
+ private static final long serialVersionUID = 8441244627425629412L;
25
+ private transient IRubyObject io;
26
+ private transient ByteBuffer buffer;
26
27
  private boolean writeMode;
27
- private Encoding binaryEncoding;
28
+ private transient Encoding binaryEncoding;
28
29
 
29
30
  private static final int CACHE_LINE_SIZE = 64;
30
31
  private static final int ARRAY_HEADER_SIZE = 24;
@@ -49,7 +50,7 @@ public class Buffer extends RubyObject {
49
50
  }
50
51
  this.buffer = ByteBuffer.allocate(CACHE_LINE_SIZE - ARRAY_HEADER_SIZE);
51
52
  this.writeMode = true;
52
- this.binaryEncoding = ctx.getRuntime().getEncodingService().getAscii8bitEncoding();
53
+ this.binaryEncoding = ctx.runtime.getEncodingService().getAscii8bitEncoding();
53
54
  return this;
54
55
  }
55
56
 
@@ -87,17 +88,17 @@ public class Buffer extends RubyObject {
87
88
  writeMode = true;
88
89
  }
89
90
  buffer.clear();
90
- return ctx.getRuntime().getNil();
91
+ return ctx.runtime.getNil();
91
92
  }
92
93
 
93
94
  @JRubyMethod(name = "size")
94
95
  public IRubyObject size(ThreadContext ctx) {
95
- return ctx.getRuntime().newFixnum(rawSize());
96
+ return ctx.runtime.newFixnum(rawSize());
96
97
  }
97
98
 
98
99
  @JRubyMethod(name = "empty?")
99
100
  public IRubyObject isEmpty(ThreadContext ctx) {
100
- return rawSize() == 0 ? ctx.getRuntime().getTrue() : ctx.getRuntime().getFalse();
101
+ return rawSize() == 0 ? ctx.runtime.getTrue() : ctx.runtime.getFalse();
101
102
  }
102
103
 
103
104
  private IRubyObject bufferWrite(ThreadContext ctx, IRubyObject str) {
@@ -105,7 +106,7 @@ public class Buffer extends RubyObject {
105
106
  int length = bytes.length();
106
107
  ensureRemainingCapacity(length);
107
108
  buffer.put(bytes.unsafeBytes(), bytes.begin(), length);
108
- return ctx.getRuntime().newFixnum(length);
109
+ return ctx.runtime.newFixnum(length);
109
110
 
110
111
  }
111
112
 
@@ -131,19 +132,19 @@ public class Buffer extends RubyObject {
131
132
  length = (int) args[0].convertToInteger().getLongValue();
132
133
  }
133
134
  if (raiseOnUnderflow && rawSize() < length) {
134
- throw ctx.getRuntime().newEOFError();
135
+ throw ctx.runtime.newEOFError();
135
136
  }
136
137
  int readLength = Math.min(length, rawSize());
137
138
  if (readLength == 0 && length > 0) {
138
- return ctx.getRuntime().getNil();
139
+ return ctx.runtime.getNil();
139
140
  } else if (readLength == 0) {
140
- return ctx.getRuntime().newString();
141
+ return ctx.runtime.newString();
141
142
  } else {
142
143
  ensureReadMode();
143
144
  byte[] bytes = new byte[readLength];
144
145
  buffer.get(bytes);
145
146
  ByteList byteList = new ByteList(bytes, binaryEncoding);
146
- return ctx.getRuntime().newString(byteList);
147
+ return ctx.runtime.newString(byteList);
147
148
  }
148
149
  }
149
150
 
@@ -161,12 +162,12 @@ public class Buffer extends RubyObject {
161
162
  feed(ctx);
162
163
  int length = (int) _length.convertToInteger().getLongValue();
163
164
  if (raiseOnUnderflow && rawSize() < length) {
164
- throw ctx.getRuntime().newEOFError();
165
+ throw ctx.runtime.newEOFError();
165
166
  }
166
167
  ensureReadMode();
167
168
  int skipLength = Math.min(length, rawSize());
168
169
  buffer.position(buffer.position() + skipLength);
169
- return ctx.getRuntime().newFixnum(skipLength);
170
+ return ctx.runtime.newFixnum(skipLength);
170
171
  }
171
172
 
172
173
  @JRubyMethod(name = "skip")
@@ -188,23 +189,23 @@ public class Buffer extends RubyObject {
188
189
  ensureReadMode();
189
190
  int length = buffer.limit() - buffer.position();
190
191
  ByteList str = new ByteList(buffer.array(), buffer.position(), length, binaryEncoding, true);
191
- return ctx.getRuntime().newString(str);
192
+ return ctx.runtime.newString(str);
192
193
  }
193
194
 
194
195
  @JRubyMethod(name = "to_a")
195
196
  public IRubyObject toA(ThreadContext ctx) {
196
- return ctx.getRuntime().newArray(toS(ctx));
197
+ return ctx.runtime.newArray(toS(ctx));
197
198
  }
198
199
 
199
200
  @JRubyMethod(name = "io")
200
201
  public IRubyObject getIo(ThreadContext ctx) {
201
- return io == null ? ctx.getRuntime().getNil() : io;
202
+ return io == null ? ctx.runtime.getNil() : io;
202
203
  }
203
204
 
204
205
  @JRubyMethod(name = "flush")
205
206
  public IRubyObject flush(ThreadContext ctx) {
206
207
  if (io == null) {
207
- return ctx.getRuntime().getNil();
208
+ return ctx.runtime.getNil();
208
209
  } else {
209
210
  return io.callMethod(ctx, "flush");
210
211
  }
@@ -213,7 +214,7 @@ public class Buffer extends RubyObject {
213
214
  @JRubyMethod(name = "close")
214
215
  public IRubyObject close(ThreadContext ctx) {
215
216
  if (io == null) {
216
- return ctx.getRuntime().getNil();
217
+ return ctx.runtime.getNil();
217
218
  } else {
218
219
  return io.callMethod(ctx, "close");
219
220
  }
@@ -223,4 +224,10 @@ public class Buffer extends RubyObject {
223
224
  public IRubyObject writeTo(ThreadContext ctx, IRubyObject io) {
224
225
  return io.callMethod(ctx, "write", readCommon(ctx, null, false));
225
226
  }
227
+
228
+ public ByteList getBytes() {
229
+ byte[] bytes = new byte[rawSize()];
230
+ buffer.get(bytes);
231
+ return new ByteList(bytes, binaryEncoding);
232
+ }
226
233
  }
@@ -14,6 +14,7 @@ import org.jruby.RubyBignum;
14
14
  import org.jruby.RubyString;
15
15
  import org.jruby.RubyArray;
16
16
  import org.jruby.RubyHash;
17
+ import org.jruby.RubyInteger;
17
18
  import org.jruby.exceptions.RaiseException;
18
19
  import org.jruby.runtime.builtin.IRubyObject;
19
20
  import org.jruby.util.ByteList;
@@ -35,7 +36,7 @@ public class Decoder implements Iterator<IRubyObject> {
35
36
  private final RubyClass unexpectedTypeErrorClass;
36
37
  private final RubyClass unknownExtTypeErrorClass;
37
38
 
38
- private ExtensionRegistry registry;
39
+ private Unpacker unpacker;
39
40
  private ByteBuffer buffer;
40
41
  private boolean symbolizeKeys;
41
42
  private boolean freeze;
@@ -45,29 +46,29 @@ public class Decoder implements Iterator<IRubyObject> {
45
46
  this(runtime, null, new byte[] {}, 0, 0, false, false, false);
46
47
  }
47
48
 
48
- public Decoder(Ruby runtime, ExtensionRegistry registry) {
49
- this(runtime, registry, new byte[] {}, 0, 0, false, false, false);
49
+ public Decoder(Ruby runtime, Unpacker unpacker) {
50
+ this(runtime, unpacker, new byte[] {}, 0, 0, false, false, false);
50
51
  }
51
52
 
52
53
  public Decoder(Ruby runtime, byte[] bytes) {
53
54
  this(runtime, null, bytes, 0, bytes.length, false, false, false);
54
55
  }
55
56
 
56
- public Decoder(Ruby runtime, ExtensionRegistry registry, byte[] bytes) {
57
- this(runtime, registry, bytes, 0, bytes.length, false, false, false);
57
+ public Decoder(Ruby runtime, Unpacker unpacker, byte[] bytes) {
58
+ this(runtime, unpacker, bytes, 0, bytes.length, false, false, false);
58
59
  }
59
60
 
60
- public Decoder(Ruby runtime, ExtensionRegistry registry, byte[] bytes, boolean symbolizeKeys, boolean freeze, boolean allowUnknownExt) {
61
- this(runtime, registry, bytes, 0, bytes.length, symbolizeKeys, freeze, allowUnknownExt);
61
+ public Decoder(Ruby runtime, Unpacker unpacker, byte[] bytes, boolean symbolizeKeys, boolean freeze, boolean allowUnknownExt) {
62
+ this(runtime, unpacker, bytes, 0, bytes.length, symbolizeKeys, freeze, allowUnknownExt);
62
63
  }
63
64
 
64
- public Decoder(Ruby runtime, ExtensionRegistry registry, byte[] bytes, int offset, int length) {
65
- this(runtime, registry, bytes, offset, length, false, false, false);
65
+ public Decoder(Ruby runtime, Unpacker unpacker, byte[] bytes, int offset, int length) {
66
+ this(runtime, unpacker, bytes, offset, length, false, false, false);
66
67
  }
67
68
 
68
- public Decoder(Ruby runtime, ExtensionRegistry registry, byte[] bytes, int offset, int length, boolean symbolizeKeys, boolean freeze, boolean allowUnknownExt) {
69
+ public Decoder(Ruby runtime, Unpacker unpacker, byte[] bytes, int offset, int length, boolean symbolizeKeys, boolean freeze, boolean allowUnknownExt) {
69
70
  this.runtime = runtime;
70
- this.registry = registry;
71
+ this.unpacker = unpacker;
71
72
  this.symbolizeKeys = symbolizeKeys;
72
73
  this.freeze = freeze;
73
74
  this.allowUnknownExt = allowUnknownExt;
@@ -122,7 +123,6 @@ public class Decoder implements Iterator<IRubyObject> {
122
123
  ByteList byteList = new ByteList(bytes, encoding);
123
124
  RubyString string = runtime.newString(byteList);
124
125
  if (this.freeze) {
125
- string.setFrozen(true);
126
126
  string = runtime.freezeAndDedupString(string);
127
127
  }
128
128
  return string;
@@ -140,9 +140,14 @@ public class Decoder implements Iterator<IRubyObject> {
140
140
  RubyHash hash = RubyHash.newHash(runtime);
141
141
  for (int i = 0; i < size; i++) {
142
142
  IRubyObject key = next();
143
- if (this.symbolizeKeys && key instanceof RubyString) {
143
+ if (key instanceof RubyString) {
144
+ if (this.symbolizeKeys) {
144
145
  key = ((RubyString) key).intern();
146
+ } else {
147
+ key = runtime.freezeAndDedupString((RubyString) key);
148
+ }
145
149
  }
150
+
146
151
  hash.fastASet(key, next());
147
152
  }
148
153
  return hash;
@@ -150,18 +155,21 @@ public class Decoder implements Iterator<IRubyObject> {
150
155
 
151
156
  private IRubyObject consumeExtension(int size) {
152
157
  int type = buffer.get();
153
- byte[] payload = readBytes(size);
154
-
155
- if (registry != null) {
156
- IRubyObject proc = registry.lookupUnpackerByTypeId(type);
157
- if (proc != null) {
158
- ByteList byteList = new ByteList(payload, runtime.getEncodingService().getAscii8bitEncoding());
159
- return proc.callMethod(runtime.getCurrentContext(), "call", runtime.newString(byteList));
158
+ if (unpacker != null) {
159
+ ExtensionRegistry.ExtensionEntry entry = unpacker.lookupExtensionByTypeId(type);
160
+ if (entry != null) {
161
+ IRubyObject proc = entry.getUnpackerProc();
162
+ if (entry.isRecursive()) {
163
+ return proc.callMethod(runtime.getCurrentContext(), "call", unpacker);
164
+ } else {
165
+ ByteList byteList = new ByteList(readBytes(size), runtime.getEncodingService().getAscii8bitEncoding());
166
+ return proc.callMethod(runtime.getCurrentContext(), "call", runtime.newString(byteList));
167
+ }
160
168
  }
161
169
  }
162
170
 
163
171
  if (this.allowUnknownExt) {
164
- return ExtensionValue.newExtensionValue(runtime, type, payload);
172
+ return ExtensionValue.newExtensionValue(runtime, type, readBytes(size));
165
173
  }
166
174
 
167
175
  throw runtime.newRaiseException(unknownExtTypeErrorClass, "unexpected extension type");
@@ -38,12 +38,16 @@ public class Encoder {
38
38
  private final Encoding utf8Encoding;
39
39
  private final boolean compatibilityMode;
40
40
  private final ExtensionRegistry registry;
41
+ private final Packer packer;
41
42
 
42
43
  public boolean hasSymbolExtType;
44
+ private boolean hasBigintExtType;
45
+ private boolean recursiveExtension;
43
46
 
44
47
  private ByteBuffer buffer;
45
48
 
46
- public Encoder(Ruby runtime, boolean compatibilityMode, ExtensionRegistry registry, boolean hasSymbolExtType) {
49
+ public Encoder(Ruby runtime, Packer packer, boolean compatibilityMode, ExtensionRegistry registry, boolean hasSymbolExtType, boolean hasBigintExtType) {
50
+ this.packer = packer;
47
51
  this.runtime = runtime;
48
52
  this.buffer = ByteBuffer.allocate(CACHE_LINE_SIZE - ARRAY_HEADER_SIZE);
49
53
  this.binaryEncoding = runtime.getEncodingService().getAscii8bitEncoding();
@@ -51,6 +55,7 @@ public class Encoder {
51
55
  this.compatibilityMode = compatibilityMode;
52
56
  this.registry = registry;
53
57
  this.hasSymbolExtType = hasSymbolExtType;
58
+ this.hasBigintExtType = hasBigintExtType;
54
59
  }
55
60
 
56
61
  public boolean isCompatibilityMode() {
@@ -66,9 +71,17 @@ public class Encoder {
66
71
  }
67
72
 
68
73
  private IRubyObject readRubyString() {
69
- IRubyObject str = runtime.newString(new ByteList(buffer.array(), 0, buffer.position(), binaryEncoding, false));
70
- buffer.clear();
71
- return str;
74
+ if (recursiveExtension) {
75
+ // If recursiveExtension is true, it means we re-entered encode, so we MUST NOT flush the buffer.
76
+ // Instead we return an empty string to act as a null object for the caller. The buffer will actually
77
+ // be flushed once we're done serializing the recursive extension.
78
+ // All other method that consume the buffer should do so through readRubyString or implement the same logic.
79
+ return runtime.newString();
80
+ } else {
81
+ IRubyObject str = runtime.newString(new ByteList(buffer.array(), 0, buffer.position(), binaryEncoding, false));
82
+ buffer.clear();
83
+ return str;
84
+ }
72
85
  }
73
86
 
74
87
  public IRubyObject encode(IRubyObject object) {
@@ -119,7 +132,9 @@ public class Encoder {
119
132
  } else if (object instanceof RubyFloat) {
120
133
  appendFloat((RubyFloat) object);
121
134
  } else if (object instanceof RubyString) {
122
- appendString((RubyString) object);
135
+ if (object.getType() == runtime.getString() || !tryAppendWithExtTypeLookup(object)) {
136
+ appendString((RubyString) object);
137
+ }
123
138
  } else if (object instanceof RubySymbol) {
124
139
  if (hasSymbolExtType) {
125
140
  appendOther(object, destination);
@@ -127,9 +142,13 @@ public class Encoder {
127
142
  appendString(((RubySymbol) object).asString());
128
143
  }
129
144
  } else if (object instanceof RubyArray) {
130
- appendArray((RubyArray) object);
145
+ if (object.getType() == runtime.getArray() || !tryAppendWithExtTypeLookup(object)) {
146
+ appendArray((RubyArray) object);
147
+ }
131
148
  } else if (object instanceof RubyHash) {
132
- appendHash((RubyHash) object);
149
+ if (object.getType() == runtime.getHash() || !tryAppendWithExtTypeLookup(object)) {
150
+ appendHash((RubyHash) object);
151
+ }
133
152
  } else if (object instanceof ExtensionValue) {
134
153
  appendExtensionValue((ExtensionValue) object);
135
154
  } else {
@@ -141,7 +160,10 @@ public class Encoder {
141
160
  BigInteger value = object.getBigIntegerValue();
142
161
  if (value.compareTo(RubyBignum.LONG_MIN) < 0 || value.compareTo(RubyBignum.LONG_MAX) > 0) {
143
162
  if (value.bitLength() > 64 || (value.bitLength() > 63 && value.signum() < 0)) {
144
- throw runtime.newArgumentError(String.format("Cannot pack big integer: %s", value));
163
+ if (hasBigintExtType && tryAppendWithExtTypeLookup(object)) {
164
+ return;
165
+ }
166
+ throw runtime.newRangeError(String.format("Cannot pack big integer: %s", value));
145
167
  }
146
168
  ensureRemainingCapacity(9);
147
169
  buffer.put(value.signum() < 0 ? INT64 : UINT64);
@@ -153,7 +175,7 @@ public class Encoder {
153
175
  }
154
176
 
155
177
  private void appendInteger(RubyInteger object) {
156
- long value = ((RubyInteger) object).getLongValue();
178
+ long value = object.getLongValue();
157
179
  if (value < 0) {
158
180
  if (value < Short.MIN_VALUE) {
159
181
  if (value < Integer.MIN_VALUE) {
@@ -241,7 +263,7 @@ public class Encoder {
241
263
  } else {
242
264
  ensureRemainingCapacity(5 + length);
243
265
  buffer.put(binary ? BIN32 : STR32);
244
- buffer.putInt((int) length);
266
+ buffer.putInt(length);
245
267
  }
246
268
  }
247
269
 
@@ -249,7 +271,7 @@ public class Encoder {
249
271
  Encoding encoding = object.getEncoding();
250
272
  boolean binary = !compatibilityMode && encoding == binaryEncoding;
251
273
  if (encoding != utf8Encoding && encoding != binaryEncoding) {
252
- object = (RubyString) ((RubyString) object).encode(runtime.getCurrentContext(), runtime.getEncodingService().getEncoding(utf8Encoding));
274
+ object = (RubyString)(object).encode(runtime.getCurrentContext(), runtime.getEncodingService().getEncoding(utf8Encoding));
253
275
  }
254
276
  ByteList bytes = object.getByteList();
255
277
  int length = bytes.length();
@@ -257,12 +279,12 @@ public class Encoder {
257
279
  buffer.put(bytes.unsafeBytes(), bytes.begin(), length);
258
280
  }
259
281
 
260
- private void appendArray(RubyArray object) {
282
+ private void appendArray(RubyArray<?> object) {
261
283
  appendArrayHeader(object);
262
284
  appendArrayElements(object);
263
285
  }
264
286
 
265
- private void appendArrayHeader(RubyArray object) {
287
+ private void appendArrayHeader(RubyArray<?> object) {
266
288
  appendArrayHeader(object.size());
267
289
  }
268
290
 
@@ -281,7 +303,7 @@ public class Encoder {
281
303
  }
282
304
  }
283
305
 
284
- private void appendArrayElements(RubyArray object) {
306
+ private void appendArrayElements(RubyArray<?> object) {
285
307
  int size = object.size();
286
308
  for (int i = 0; i < size; i++) {
287
309
  appendObject(object.eltOk(i));
@@ -383,25 +405,41 @@ public class Encoder {
383
405
  appendExt((int) type, payloadBytes);
384
406
  }
385
407
 
386
- private void appendOther(IRubyObject object, IRubyObject destination) {
408
+ private boolean tryAppendWithExtTypeLookup(IRubyObject object) {
387
409
  if (registry != null) {
388
- RubyModule lookupClass;
389
-
390
- if (object.getType() == runtime.getSymbol()) {
391
- lookupClass = object.getType();
392
- } else {
393
- lookupClass = object.getSingletonClass();
394
- }
395
-
396
- IRubyObject[] pair = registry.lookupPackerForObject(object);
397
- if (pair != null) {
398
- RubyString bytes = pair[0].callMethod(runtime.getCurrentContext(), "call", object).asString();
399
- int type = (int) ((RubyFixnum) pair[1]).getLongValue();
400
- appendExt(type, bytes.getByteList());
401
- return;
410
+ ExtensionRegistry.ExtensionEntry entry = registry.lookupExtensionForObject(object);
411
+ if (entry != null) {
412
+ IRubyObject proc = entry.getPackerProc();
413
+ int type = entry.getTypeId();
414
+
415
+ if (entry.isRecursive()) {
416
+ ByteBuffer oldBuffer = buffer;
417
+ buffer = ByteBuffer.allocate(CACHE_LINE_SIZE - ARRAY_HEADER_SIZE);
418
+ boolean previousRecursiveExtension = recursiveExtension;
419
+ recursiveExtension = true;
420
+
421
+ ByteList payload;
422
+ try {
423
+ IRubyObject args[] = { object, packer };
424
+ proc.callMethod(runtime.getCurrentContext(), "call", args);
425
+ payload = new ByteList(buffer.array(), 0, buffer.position(), binaryEncoding, false);
426
+ } finally {
427
+ recursiveExtension = previousRecursiveExtension;
428
+ buffer = oldBuffer;
429
+ }
430
+ appendExt(type, payload);
431
+ } else {
432
+ RubyString bytes = proc.callMethod(runtime.getCurrentContext(), "call", object).asString();
433
+ appendExt(type, bytes.getByteList());
434
+ }
435
+ return true;
402
436
  }
403
437
  }
404
- appendCustom(object, destination);
438
+ return false;
439
+ }
440
+
441
+ private void appendOther(IRubyObject object, IRubyObject destination) {
442
+ if (!tryAppendWithExtTypeLookup(object)) { appendCustom(object, destination); }
405
443
  }
406
444
 
407
445
  private void appendCustom(IRubyObject object, IRubyObject destination) {