From 564dcf5d8a03fdc23527daaca093178f676a1242 Mon Sep 17 00:00:00 2001 From: Shizuo Fujita Date: Fri, 7 Aug 2026 05:01:47 +0900 Subject: [PATCH] Add a rake spec:valgrind task using ruby_memcheck Running the suite under Valgrind on a Ruby C extension is normally impractical: the interpreter itself produces a large volume of reports that have nothing to do with the extension. ruby_memcheck wraps Valgrind and only surfaces errors whose stack trace passes through the extension's .so, which makes the output usable. It is the same tool Shopify runs on nokogiri and liquid-c. rake spec:valgrind The task is Linux-only, since Valgrind is not usable in practice on macOS or Windows, and the dependency carries the same guard so bundle install keeps working there. If ruby_memcheck is not installed the task is simply not defined, rather than breaking the Rakefile. YJIT is disabled under Valgrind because the two interfere, adding noise and slowdown. The suite reports nothing on the current tree, so no suppression file is needed. Verified that the task has teeth by running it against the use-after-free of a stream's borrowed CDict/DDict: it reports 113 Invalid read records, correctly attributed through ZSTD_CCtx_init_compressStream2 and ZSTD_decompressBegin_usingDDict inside zstdruby.so. Co-Authored-By: Claude Opus 5 --- Gemfile | 2 ++ Rakefile | 28 ++++++++++++++++++++++++++++ 2 files changed, 30 insertions(+) diff --git a/Gemfile b/Gemfile index afffcc2..2c76704 100644 --- a/Gemfile +++ b/Gemfile @@ -2,3 +2,5 @@ source 'https://rubygems.org' # Specify your gem's dependencies in zstd_ruby.gemspec gemspec + +gem 'ruby_memcheck', '~> 3.0' if RUBY_PLATFORM.include?('linux') diff --git a/Rakefile b/Rakefile index f76e1c2..a2eb7e5 100644 --- a/Rakefile +++ b/Rakefile @@ -15,6 +15,34 @@ end task :default => [:clobber, :compile, :spec] +if RUBY_PLATFORM.include?('linux') + begin + require 'ruby_memcheck' + require 'ruby_memcheck/rspec/rake_task' + + RubyMemcheck.config( + binary_name: 'zstdruby', + # Valgrind and YJIT interfere with each other, adding noise and slowdown, + # so keep YJIT disabled while running under Valgrind. + ruby: "#{FileUtils::RUBY} --disable-yjit" + ) + + namespace :spec do + task :check_valgrind do + unless system('command -v valgrind > /dev/null 2>&1') + abort("\nValgrind is required for `rake spec:valgrind` but was not found.\n" \ + "Install it first (Linux only), e.g. `sudo apt-get install valgrind`.\n") + end + end + + RubyMemcheck::RSpec::RakeTask.new(valgrind: [:check_valgrind, :compile]) + end + rescue LoadError + # ruby_memcheck is an optional, Linux-only development dependency. If it is + # not installed just skip defining the task instead of breaking the Rakefile. + end +end + desc 'Sync zstd libs dirs to ext/zstdruby/libzstd' task :zstd_update do FileUtils.rm_r("ext/zstdruby/libzstd")