zstd-ruby 2.0.8 → 2.0.10

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 2747a2ccb21d362d5523b5c986fd5f45e7bc2007a40710c303e14e69b66dd626
4
- data.tar.gz: 9b396596f0877e8da07fedf4a90e5d54af41414262dd12754faff6b71e56b771
3
+ metadata.gz: 02555a47928cf342248cbe5335c2a6b4aad14e3c7f9d78bbb65bf83fa3791c16
4
+ data.tar.gz: b58b8db365ce2490998468d785f77b5500023d9f4840aa49ade89413308c9672
5
5
  SHA512:
6
- metadata.gz: 5cdeca9f57890066be8ad60fa7768b0920faac9bae169f6ca5069302a38e48a8c1e22d6b93a13abda415447dc7d750f38aef923527d055d04075d0d81d9a6556
7
- data.tar.gz: a625f95323296768a68ccbc98fa4a24175d8a6d2643b3ef7ee4bf3b7cb1ceef2afc4ba4ba263b7bf2b7dbbc279ae89231f92d0d043aa4a1c6d96e8b8e03aa776
6
+ metadata.gz: c2a700c515e5073d5a20b7383a8c890007aa2792db0fc27e5187bbfd2a3ff806929349d988e8809a29ac0d6616a12f3fab41583c8ab10afe4fd220a35da35e8b
7
+ data.tar.gz: e560ce637e53be29601f6cb610461a8c137808019902c1c2e461390400845a021e22a5749020ec3e6f842a94d02ecfae9fe155ef39485db6dcc7503b23d53967
data/.gitignore CHANGED
@@ -22,3 +22,4 @@ vendor/
22
22
  .ruby-version
23
23
 
24
24
  .vscode
25
+ mise.toml
data/Gemfile CHANGED
@@ -2,3 +2,7 @@ source 'https://rubygems.org'
2
2
 
3
3
  # Specify your gem's dependencies in zstd_ruby.gemspec
4
4
  gemspec
5
+
6
+ if RUBY_PLATFORM.include?('linux') && Gem::Version.new(RUBY_VERSION) >= Gem::Version.new('3.0.0')
7
+ gem 'ruby_memcheck', '~> 3.0'
8
+ end
data/Rakefile CHANGED
@@ -15,6 +15,32 @@ end
15
15
 
16
16
  task :default => [:clobber, :compile, :spec]
17
17
 
18
+ begin
19
+ require 'ruby_memcheck'
20
+ require 'ruby_memcheck/rspec/rake_task'
21
+
22
+ RubyMemcheck.config(
23
+ binary_name: 'zstdruby',
24
+ # Valgrind and YJIT interfere with each other, adding noise and slowdown,
25
+ # so keep YJIT disabled while running under Valgrind.
26
+ ruby: "#{FileUtils::RUBY} --disable-yjit"
27
+ )
28
+
29
+ namespace :spec do
30
+ task :check_valgrind do
31
+ unless system('command -v valgrind > /dev/null 2>&1')
32
+ abort("\nValgrind is required for `rake spec:valgrind` but was not found.\n" \
33
+ "Install it first (Linux only), e.g. `sudo apt-get install valgrind`.\n")
34
+ end
35
+ end
36
+
37
+ RubyMemcheck::RSpec::RakeTask.new(valgrind: [:check_valgrind, :compile])
38
+ end
39
+ rescue LoadError
40
+ # ruby_memcheck is an optional development dependency, absent on the platforms
41
+ # and Ruby versions the Gemfile excludes. Skip the task instead of breaking.
42
+ end
43
+
18
44
  desc 'Sync zstd libs dirs to ext/zstdruby/libzstd'
19
45
  task :zstd_update do
20
46
  FileUtils.rm_r("ext/zstdruby/libzstd")
@@ -24,7 +24,11 @@ static int convert_compression_level(ZSTD_CCtx* ctx, VALUE compression_level_val
24
24
  return NUM2INT(compression_level_value);
25
25
  }
26
26
 
27
- static void set_compress_params(ZSTD_CCtx* const ctx, VALUE kwargs)
27
+ /* Returns the Zstd::CDict given as `dict:`, or Qnil. ZSTD_CCtx_refCDict only
28
+ borrows the pointer, so a caller that keeps the ZSTD_CCtx alive beyond this
29
+ call has to keep the returned object reachable for just as long. A String
30
+ dictionary needs no such handling: ZSTD_CCtx_loadDictionary copies it. */
31
+ static VALUE set_compress_params(ZSTD_CCtx* const ctx, VALUE kwargs)
28
32
  {
29
33
  ID kwargs_keys[2];
30
34
  kwargs_keys[0] = rb_intern("level");
@@ -46,6 +50,7 @@ static void set_compress_params(ZSTD_CCtx* const ctx, VALUE kwargs)
46
50
  ZSTD_freeCCtx(ctx);
47
51
  rb_raise(rb_eRuntimeError, "%s", "ZSTD_CCtx_refCDict failed");
48
52
  }
53
+ return kwargs_values[1];
49
54
  } else if (TYPE(kwargs_values[1]) == T_STRING) {
50
55
  char* dict_buffer = RSTRING_PTR(kwargs_values[1]);
51
56
  size_t dict_size = RSTRING_LEN(kwargs_values[1]);
@@ -59,6 +64,7 @@ static void set_compress_params(ZSTD_CCtx* const ctx, VALUE kwargs)
59
64
  rb_raise(rb_eArgError, "`dict:` must be a Zstd::CDict or a String");
60
65
  }
61
66
  }
67
+ return Qnil;
62
68
  }
63
69
 
64
70
  struct stream_compress_params {
@@ -122,7 +128,9 @@ static size_t zstd_compress(ZSTD_CCtx* const ctx, char* output_data, size_t outp
122
128
  #endif
123
129
  }
124
130
 
125
- static void set_decompress_params(ZSTD_DCtx* const dctx, VALUE kwargs)
131
+ /* Returns the Zstd::DDict given as `dict:`, or Qnil. See set_compress_params:
132
+ ZSTD_DCtx_refDDict borrows, ZSTD_DCtx_loadDictionary copies. */
133
+ static VALUE set_decompress_params(ZSTD_DCtx* const dctx, VALUE kwargs)
126
134
  {
127
135
  ID kwargs_keys[1];
128
136
  kwargs_keys[0] = rb_intern("dict");
@@ -137,19 +145,21 @@ static void set_decompress_params(ZSTD_DCtx* const dctx, VALUE kwargs)
137
145
  ZSTD_freeDCtx(dctx);
138
146
  rb_raise(rb_eRuntimeError, "%s", "ZSTD_DCtx_refDDict failed");
139
147
  }
148
+ return kwargs_values[0];
140
149
  } else if (TYPE(kwargs_values[0]) == T_STRING) {
141
150
  char* dict_buffer = RSTRING_PTR(kwargs_values[0]);
142
151
  size_t dict_size = RSTRING_LEN(kwargs_values[0]);
143
152
  size_t load_dict_ret = ZSTD_DCtx_loadDictionary(dctx, dict_buffer, dict_size);
144
153
  if (ZSTD_isError(load_dict_ret)) {
145
154
  ZSTD_freeDCtx(dctx);
146
- rb_raise(rb_eRuntimeError, "%s", "ZSTD_CCtx_loadDictionary failed");
155
+ rb_raise(rb_eRuntimeError, "%s", "ZSTD_DCtx_loadDictionary failed");
147
156
  }
148
157
  } else {
149
158
  ZSTD_freeDCtx(dctx);
150
159
  rb_raise(rb_eArgError, "`dict:` must be a Zstd::DDict or a String");
151
160
  }
152
161
  }
162
+ return Qnil;
153
163
  }
154
164
 
155
165
  struct stream_decompress_params {
@@ -181,35 +191,4 @@ static size_t zstd_stream_decompress(ZSTD_DCtx* const dctx, ZSTD_outBuffer* outp
181
191
  #endif
182
192
  }
183
193
 
184
- struct decompress_params {
185
- ZSTD_DCtx* dctx;
186
- char* output_data;
187
- size_t output_size;
188
- char* input_data;
189
- size_t input_size;
190
- size_t ret;
191
- };
192
-
193
- static void* decompress_wrapper(void* args)
194
- {
195
- struct decompress_params* params = args;
196
- params->ret = ZSTD_decompressDCtx(params->dctx, params->output_data, params->output_size, params->input_data, params->input_size);
197
- return NULL;
198
- }
199
-
200
- static size_t zstd_decompress(ZSTD_DCtx* const dctx, char* output_data, size_t output_size, char* input_data, size_t input_size, bool gvl)
201
- {
202
- #ifdef HAVE_RUBY_THREAD_H
203
- if (gvl) {
204
- return ZSTD_decompressDCtx(dctx, output_data, output_size, input_data, input_size);
205
- } else {
206
- struct decompress_params params = { dctx, output_data, output_size, input_data, input_size };
207
- rb_thread_call_without_gvl(decompress_wrapper, &params, NULL, NULL);
208
- return params.ret;
209
- }
210
- #else
211
- return ZSTD_decompressDCtx(dctx, output_data, output_size, input_data, input_size);
212
- #endif
213
- }
214
-
215
194
  #endif /* ZSTD_RUBY_H */
@@ -26,7 +26,7 @@ def ext_export_filename
26
26
  name
27
27
  end
28
28
 
29
- $CFLAGS = '-I. -O3 -std=c99 -DZSTD_STATIC_LINKING_ONLY -DZSTD_MULTITHREAD -pthread -DDEBUGLEVEL=0 -fvisibility=hidden -DZSTDLIB_VISIBLE=\'__attribute__((visibility("hidden")))\' -DZSTDLIB_HIDDEN=\'__attribute__((visibility("hidden")))\''
29
+ $CFLAGS += ' -I. -O3 -std=c99 -DZSTD_STATIC_LINKING_ONLY -DZSTD_MULTITHREAD -pthread -DDEBUGLEVEL=0 -fvisibility=hidden -DZSTDLIB_VISIBLE=\'__attribute__((visibility("hidden")))\' -DZSTDLIB_HIDDEN=\'__attribute__((visibility("hidden")))\''
30
30
  $CPPFLAGS += " -fdeclspec" if CONFIG['CXX'] =~ /clang/
31
31
 
32
32
  # macOS specific: Use exported_symbols_list to control symbol visibility
@@ -5,6 +5,7 @@ struct streaming_compress_t {
5
5
  VALUE buf;
6
6
  size_t buf_size;
7
7
  VALUE pending; /* accumulate compressed bytes produced by write() */
8
+ VALUE dict; /* Zstd::CDict the ctx borrows a pointer into, or Qnil */
8
9
  };
9
10
 
10
11
  static void
@@ -14,9 +15,11 @@ streaming_compress_mark(void *p)
14
15
  #ifdef HAVE_RB_GC_MARK_MOVABLE
15
16
  rb_gc_mark_movable(sc->buf);
16
17
  rb_gc_mark_movable(sc->pending);
18
+ rb_gc_mark_movable(sc->dict);
17
19
  #else
18
20
  rb_gc_mark(sc->buf);
19
21
  rb_gc_mark(sc->pending);
22
+ rb_gc_mark(sc->dict);
20
23
  #endif
21
24
  }
22
25
 
@@ -44,6 +47,7 @@ streaming_compress_compact(void *p)
44
47
  struct streaming_compress_t *sc = p;
45
48
  sc->buf = rb_gc_location(sc->buf);
46
49
  sc->pending = rb_gc_location(sc->pending);
50
+ sc->dict = rb_gc_location(sc->dict);
47
51
  }
48
52
  #endif
49
53
 
@@ -69,6 +73,7 @@ rb_streaming_compress_allocate(VALUE klass)
69
73
  RB_OBJ_WRITE(obj, &sc->buf, Qnil);
70
74
  sc->buf_size = 0;
71
75
  RB_OBJ_WRITE(obj, &sc->pending, Qnil);
76
+ RB_OBJ_WRITE(obj, &sc->dict, Qnil);
72
77
  return obj;
73
78
  }
74
79
 
@@ -86,9 +91,10 @@ rb_streaming_compress_initialize(int argc, VALUE *argv, VALUE obj)
86
91
  if (ctx == NULL) {
87
92
  rb_raise(rb_eRuntimeError, "%s", "ZSTD_createCCtx error");
88
93
  }
89
- set_compress_params(ctx, kwargs);
94
+ VALUE dict = set_compress_params(ctx, kwargs);
90
95
 
91
96
  sc->ctx = ctx;
97
+ RB_OBJ_WRITE(obj, &sc->dict, dict);
92
98
  RB_OBJ_WRITE(obj, &sc->buf, rb_str_new(NULL, buffOutSize));
93
99
  sc->buf_size = buffOutSize;
94
100
  RB_OBJ_WRITE(obj, &sc->pending, rb_str_new(0, 0));
@@ -205,6 +211,8 @@ rb_streaming_compress_flush(VALUE obj)
205
211
  VALUE drained = no_compress(sc, ZSTD_e_flush);
206
212
  VALUE out = rb_str_dup(sc->pending);
207
213
  rb_str_cat(out, RSTRING_PTR(drained), RSTRING_LEN(drained));
214
+ /* prevent `drained` from being freed by GC inside rb_str_cat */
215
+ RB_GC_GUARD(drained);
208
216
  rb_str_resize(sc->pending, 0);
209
217
  return out;
210
218
  }
@@ -217,6 +225,8 @@ rb_streaming_compress_finish(VALUE obj)
217
225
  VALUE drained = no_compress(sc, ZSTD_e_end);
218
226
  VALUE out = rb_str_dup(sc->pending);
219
227
  rb_str_cat(out, RSTRING_PTR(drained), RSTRING_LEN(drained));
228
+ /* prevent `drained` from being freed by GC inside rb_str_cat */
229
+ RB_GC_GUARD(drained);
220
230
  rb_str_resize(sc->pending, 0);
221
231
  return out;
222
232
  }
@@ -4,6 +4,7 @@ struct streaming_decompress_t {
4
4
  ZSTD_DCtx* dctx;
5
5
  VALUE buf;
6
6
  size_t buf_size;
7
+ VALUE dict; /* Zstd::DDict the dctx borrows a pointer into, or Qnil */
7
8
  };
8
9
 
9
10
  static void
@@ -12,8 +13,10 @@ streaming_decompress_mark(void *p)
12
13
  struct streaming_decompress_t *sd = p;
13
14
  #ifdef HAVE_RB_GC_MARK_MOVABLE
14
15
  rb_gc_mark_movable(sd->buf);
16
+ rb_gc_mark_movable(sd->dict);
15
17
  #else
16
18
  rb_gc_mark(sd->buf);
19
+ rb_gc_mark(sd->dict);
17
20
  #endif
18
21
  }
19
22
 
@@ -40,6 +43,7 @@ streaming_decompress_compact(void *p)
40
43
  {
41
44
  struct streaming_decompress_t *sd = p;
42
45
  sd->buf = rb_gc_location(sd->buf);
46
+ sd->dict = rb_gc_location(sd->dict);
43
47
  }
44
48
  #endif
45
49
 
@@ -64,6 +68,7 @@ rb_streaming_decompress_allocate(VALUE klass)
64
68
  sd->dctx = NULL;
65
69
  RB_OBJ_WRITE(obj, &sd->buf, Qnil);
66
70
  sd->buf_size = 0;
71
+ RB_OBJ_WRITE(obj, &sd->dict, Qnil);
67
72
  return obj;
68
73
  }
69
74
 
@@ -81,9 +86,10 @@ rb_streaming_decompress_initialize(int argc, VALUE *argv, VALUE obj)
81
86
  if (dctx == NULL) {
82
87
  rb_raise(rb_eRuntimeError, "%s", "ZSTD_createDCtx error");
83
88
  }
84
- set_decompress_params(dctx, kwargs);
89
+ VALUE dict = set_decompress_params(dctx, kwargs);
85
90
 
86
91
  sd->dctx = dctx;
92
+ RB_OBJ_WRITE(obj, &sd->dict, dict);
87
93
  RB_OBJ_WRITE(obj, &sd->buf, rb_str_new(NULL, buffOutSize));
88
94
  sd->buf_size = buffOutSize;
89
95
 
@@ -51,7 +51,8 @@ static VALUE decode_one_frame(ZSTD_DCtx* dctx, const unsigned char* src, size_t
51
51
 
52
52
  for (;;) {
53
53
  ZSTD_outBuffer o = (ZSTD_outBuffer){ buf, cap, 0 };
54
- size_t ret = ZSTD_decompressStream(dctx, &o, &in);
54
+ size_t const in_pos_before = in.pos;
55
+ size_t ret = zstd_stream_decompress(dctx, &o, &in, false);
55
56
  if (ZSTD_isError(ret)) {
56
57
  xfree(buf);
57
58
  rb_raise(rb_eRuntimeError, "ZSTD_decompressStream failed: %s", ZSTD_getErrorName(ret));
@@ -62,6 +63,13 @@ static VALUE decode_one_frame(ZSTD_DCtx* dctx, const unsigned char* src, size_t
62
63
  if (ret == 0) {
63
64
  break;
64
65
  }
66
+ /* A non-zero return is a "need more input" hint, not an error, and libzstd's
67
+ own noForwardProgress guard is bypassed by the early return it takes on a
68
+ truncated frame header -- so the stall has to be detected here. */
69
+ if (o.pos == 0 && in.pos == in_pos_before) {
70
+ xfree(buf);
71
+ rb_raise(rb_eRuntimeError, "ZSTD_decompressStream failed: truncated or incomplete frame");
72
+ }
65
73
  }
66
74
  xfree(buf);
67
75
  if (consumed) {
@@ -70,10 +78,6 @@ static VALUE decode_one_frame(ZSTD_DCtx* dctx, const unsigned char* src, size_t
70
78
  return out;
71
79
  }
72
80
 
73
- static VALUE decompress_buffered(ZSTD_DCtx* dctx, const char* data, size_t len) {
74
- return decode_one_frame(dctx, (const unsigned char*)data, len, Qnil, NULL);
75
- }
76
-
77
81
  static VALUE rb_decompress(int argc, VALUE *argv, VALUE self)
78
82
  {
79
83
  VALUE input_value, kwargs;
@@ -124,6 +128,7 @@ static VALUE rb_decompress(int argc, VALUE *argv, VALUE self)
124
128
  result = out;
125
129
  } else {
126
130
  rb_str_cat(result, RSTRING_PTR(out), RSTRING_LEN(out));
131
+ RB_GC_GUARD(out);
127
132
  }
128
133
 
129
134
  if (consumed == 0) {
@@ -208,7 +213,7 @@ static VALUE rb_cdict_initialize(int argc, VALUE *argv, VALUE self)
208
213
 
209
214
  static VALUE rb_ddict_alloc(VALUE self)
210
215
  {
211
- ZSTD_CDict* ddict = NULL;
216
+ ZSTD_DDict* ddict = NULL;
212
217
  return TypedData_Wrap_Struct(self, &ddict_type, ddict);
213
218
  }
214
219
 
@@ -1,3 +1,3 @@
1
1
  module Zstd
2
- VERSION = "2.0.8"
2
+ VERSION = "2.0.10"
3
3
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: zstd-ruby
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.0.8
4
+ version: 2.0.10
5
5
  platform: ruby
6
6
  authors:
7
7
  - SpringMT
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2026-08-09 00:00:00.000000000 Z
11
+ date: 2026-09-23 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler