ruby_memcheck 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 84b08d7b0d1f0f1a254be4d2634c44055c21c415ccfa12d69c9bef56763291bf
4
+ data.tar.gz: b73189e2d75124b4117565ba97a2fadf1a1b8ef9963bf6d8528fb04bd19c0b33
5
+ SHA512:
6
+ metadata.gz: 682b181ca12116b0383a317304e9f931b4ccc4459259ef87b5d7823641ae73ec1077954e0a80b704ee20d525d39f01ea12ded58e24e39c48e69a065e79811873
7
+ data.tar.gz: 82ea0151eebe57bcc43cc7316ba93ad6bb8af5413c311ffd48d78ebf105091774a1c28364a8acef7b4dafcfb4cc21fa4a542f43457bda44a69b8f63b9294f177
@@ -0,0 +1,12 @@
1
+ name: Lint
2
+ on: [push, pull_request]
3
+ jobs:
4
+ rubocop:
5
+ runs-on: ubuntu-latest
6
+ steps:
7
+ - uses: actions/checkout@v2
8
+ - uses: ruby/setup-ruby@v1
9
+ with:
10
+ ruby-version: '3.0'
11
+ - run: bundle install --jobs=3 --retry=3 --path=vendor/bundle
12
+ - run: bundle exec rubocop
@@ -0,0 +1,22 @@
1
+ name: Test
2
+ on: [push, pull_request]
3
+ jobs:
4
+ test:
5
+ runs-on: ubuntu-latest
6
+ strategy:
7
+ matrix:
8
+ entry:
9
+ - { ruby: '2.6', allowed-failure: false }
10
+ - { ruby: '2.7', allowed-failure: false }
11
+ - { ruby: '3.0', allowed-failure: false }
12
+ - { ruby: ruby-head, allowed-failure: false }
13
+ name: ruby ${{ matrix.entry.ruby }}
14
+ steps:
15
+ - uses: actions/checkout@v2
16
+ - uses: ruby/setup-ruby@v1
17
+ with:
18
+ ruby-version: ${{ matrix.entry.ruby }}
19
+ - run: sudo apt-get install -y valgrind
20
+ - run: bundle install --jobs=3 --retry=3
21
+ - run: bundle exec rake
22
+ continue-on-error: ${{ matrix.entry.allowed-failure }}
data/.gitignore ADDED
@@ -0,0 +1,12 @@
1
+ Gemfile.lock
2
+
3
+ /.bundle/
4
+ /.yardoc
5
+ /_yardoc/
6
+ /coverage/
7
+ /doc/
8
+ /pkg/
9
+ /spec/reports/
10
+ /tmp/
11
+
12
+ *.so
data/.rubocop.yml ADDED
@@ -0,0 +1,5 @@
1
+ inherit_gem:
2
+ rubocop-shopify: rubocop.yml
3
+
4
+ AllCops:
5
+ SuggestExtensions: false
data/Gemfile ADDED
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ source "https://rubygems.org"
4
+
5
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2021 Peter Zhu
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,97 @@
1
+ # ruby_memcheck
2
+
3
+ This gem provides a sane way to use Valgrind's memcheck on your native extension gem.
4
+
5
+ ## Table of contents
6
+
7
+ 1. [What is this gem?](#what-is-this-gem)
8
+ 1. [Who should use this gem?](#who-should-use-this-gem)
9
+ 1. [How does it work?](#how-does-it-work)
10
+ 1. [Limitations](#limitations)
11
+ 1. [Installation](#installation)
12
+ 1. [Usage](#usage)
13
+
14
+ ## What is this gem?
15
+
16
+ Valgrind's memcheck is a great tool to find and debug memory issues (e.g. memory leak, use-after-free, etc.). However, it doesn't work well on Ruby because Ruby does not free all of the memory it allocates during shutdown. This results in Valgrind reporting thousands (or more) false-positives, making it very difficult for Valgrind to actually be useful. This gem solves the problem by using heuristics to filter out false-positives.
17
+
18
+ ### Who should use this gem?
19
+
20
+ Only gems with native extensions can use this gem. If your gem is written in plain Ruby, this gem is not useful for you.
21
+
22
+ ### How does it work?
23
+
24
+ This gem runs Valgrind with the `--xml` option to generate a XML of all the errors. It will then parse the XML and use various heuristics based on the type of the error and the stack trace to filter out errors that are false-positives.
25
+
26
+ ### Limitations
27
+
28
+ Because of the aggressive heuristics used to filter out false-positives, there are various limitations of what this gem can detect.
29
+
30
+ 1. This gem is only expected to work on Linux.
31
+ 1. It will not find memory leaks in Ruby. It filters out everything in Ruby.
32
+ 1. It will not find memory leaks of allocations that occurred in Ruby (even if the memory leak is caused by your native extension).
33
+
34
+ An example of this is if a string is allocated in Ruby, passed into your native extension, you change the pointer of the string without freeing the contents, so the contents of the string becomes leaked.
35
+ 1. To filter out false-positives, it will only find definite leaks (i.e. memory regions with no pointers to it). It will not find possible leaks (i.e. memory regions with pointers to it).
36
+ 1. It will not find leaks that occur in the `Init` function of your native extension.
37
+ 1. It will not find uses of undefined values (e.g. conditional jumps depending on undefined values). This is just a technical limitation that has not been solved yet (contributions welcome!).
38
+
39
+ ## Installation
40
+
41
+ Add this line to your application's Gemfile:
42
+
43
+ ```ruby
44
+ gem "ruby_memcheck"
45
+ ```
46
+
47
+ ## Usage
48
+
49
+ The easiest way to use this gem is to use it on your test suite using rake.
50
+
51
+ 0. Install Valgrind.
52
+ 1. In your Rakefile, require this gem.
53
+
54
+ ```ruby
55
+ require "ruby_memcheck"
56
+ ```
57
+ 1. Configure the gem by calling `RubyMemcheck.config`. You must pass it your binary name. This is the same value you passed into `create_makefile` in your `extconf.rb` file. Make sure this value is correct or it will filter out almost everything as a false-positive!
58
+
59
+ ```ruby
60
+ RubyMemcheck.config(binary_name: "your_binary_name")
61
+ ```
62
+ 1. Locate your test task(s) in your Rakefile. You can identify it with a call to `Rake::TestTask.new`.
63
+ 1. Create a namespace under the test task and create a `RubyMemcheck::TestTask` with the same configuration.
64
+
65
+ For example, if your Rakefile looked like this before:
66
+
67
+ ```ruby
68
+ Rake::TestTask.new(test: :compile) do |t|
69
+ t.libs << "test"
70
+ t.test_files = FileList["test/unit/**/*_test.rb"]
71
+ end
72
+ ```
73
+
74
+ You can change it to look like this:
75
+
76
+ ```ruby
77
+ test_config = lambda do |t|
78
+ t.libs << "test"
79
+ t.test_files = FileList["test/**/*_test.rb"]
80
+ end
81
+ Rake::TestTask.new(test: :compile, &test_config)
82
+ namespace :test do
83
+ RubyMemcheck::TestTask.new(valgrind: :compile, &test_config)
84
+ end
85
+ ```
86
+ 1. In your `test_helper.rb`/`spec_helper.rb` (or whatever file sets up your test suite), add this line:
87
+
88
+ ```ruby
89
+ END { GC.start }
90
+ ```
91
+
92
+ This will ensure that the Garbage Collector is ran before Ruby shuts down. This will reduce the number of false-positives.
93
+ 1. You're ready to run your test suite with Valgrind using `rake test:valgrind`! Note that this will take a while to run because Valgrind will make Ruby significantly slower.
94
+
95
+ ## License
96
+
97
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
data/Rakefile ADDED
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "bundler/gem_tasks"
4
+ require "rake/testtask"
5
+ require "rake/extensiontask"
6
+
7
+ Rake::TestTask.new(test: "test:compile") do |t|
8
+ t.libs << "test"
9
+ t.libs << "lib"
10
+ t.test_files = FileList["test/**/*_test.rb"]
11
+ end
12
+
13
+ namespace :test do
14
+ Rake::ExtensionTask.new("ruby_memcheck_c_test") do |ext|
15
+ ext.ext_dir = "test/ruby_memcheck/ext"
16
+ ext.lib_dir = "test/ruby_memcheck/ext"
17
+ end
18
+ end
19
+
20
+ task default: :test
@@ -0,0 +1,90 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RubyMemcheck
4
+ class Configuration
5
+ DEFAULT_VALGRIND = "valgrind"
6
+ DEFAULT_VALGRIND_OPTIONS = [
7
+ "--num-callers=50",
8
+ "--error-limit=no",
9
+ "--undef-value-errors=no",
10
+ "--leak-check=full",
11
+ "--show-leak-kinds=definite",
12
+ ].freeze
13
+ DEFAULT_SKIPPED_RUBY_FUNCTIONS = [
14
+ /\Arb_check_funcall/,
15
+ /\Arb_enc_raise\z/,
16
+ /\Arb_funcall/,
17
+ /\Arb_ivar_set\z/,
18
+ /\Arb_raise\z/,
19
+ /\Arb_rescue/,
20
+ /\Arb_respond_to\z/,
21
+ /\Arb_yield/,
22
+ ].freeze
23
+
24
+ attr_reader :binary_name, :ruby, :valgrind_options, :valgrind,
25
+ :skipped_ruby_functions, :valgrind_xml_file, :output_io
26
+
27
+ def initialize(
28
+ binary_name:,
29
+ ruby: FileUtils::RUBY,
30
+ valgrind: DEFAULT_VALGRIND,
31
+ valgrind_options: DEFAULT_VALGRIND_OPTIONS,
32
+ skipped_ruby_functions: DEFAULT_SKIPPED_RUBY_FUNCTIONS,
33
+ valgrind_xml_file: Tempfile.new,
34
+ output_io: $stderr
35
+ )
36
+ @binary_name = binary_name
37
+ @ruby = ruby
38
+ @valgrind = valgrind
39
+ @valgrind_options = valgrind_options
40
+ @skipped_ruby_functions = skipped_ruby_functions
41
+ @output_io = output_io
42
+
43
+ if valgrind_xml_file
44
+ @valgrind_xml_file = valgrind_xml_file
45
+ @valgrind_options += [
46
+ "--xml=yes",
47
+ "--xml-file=#{valgrind_xml_file.path}",
48
+ ]
49
+ end
50
+ end
51
+
52
+ def command(*args)
53
+ "#{valgrind} #{valgrind_options.join(" ")} #{ruby} #{args.join(" ")}"
54
+ end
55
+
56
+ def skip_stack?(stack)
57
+ in_binary = false
58
+
59
+ stack.frames.each do |frame|
60
+ fn = frame.fn
61
+
62
+ if frame_in_ruby?(frame) # in ruby
63
+ unless in_binary
64
+ # Skip this stack because it was called from Ruby
65
+ return true if skipped_ruby_functions.any? { |r| r.match?(fn) }
66
+ end
67
+ elsif frame_in_binary?(frame) # in binary
68
+ in_binary = true
69
+
70
+ # Skip the Init function
71
+ return true if fn == "Init_#{binary_name}"
72
+ end
73
+ end
74
+
75
+ !in_binary
76
+ end
77
+
78
+ def frame_in_ruby?(frame)
79
+ frame.obj == ruby
80
+ end
81
+
82
+ def frame_in_binary?(frame)
83
+ if frame.obj
84
+ File.basename(frame.obj, ".*") == binary_name
85
+ else
86
+ false
87
+ end
88
+ end
89
+ end
90
+ end
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RubyMemcheck
4
+ class Frame
5
+ attr_reader :fn, :obj, :file, :line, :in_binary
6
+ alias_method :in_binary?, :in_binary
7
+
8
+ def initialize(configuration, frame_xml)
9
+ @fn = frame_xml.at_xpath("fn")&.content
10
+ @obj = frame_xml.at_xpath("obj")&.content
11
+ # file and line may not be available
12
+ @file = frame_xml.at_xpath("file")&.content
13
+ @line = frame_xml.at_xpath("line")&.content
14
+
15
+ @in_binary = configuration.frame_in_binary?(self)
16
+ end
17
+
18
+ def to_s
19
+ if file
20
+ "#{fn} (#{file}:#{line})"
21
+ elsif fn
22
+ "#{fn} (at #{obj})"
23
+ else
24
+ "<unknown stack frame>"
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RubyMemcheck
4
+ class Stack
5
+ attr_reader :frames
6
+
7
+ def initialize(configuration, stack_xml)
8
+ @frames = stack_xml.xpath("frame").map { |frame| Frame.new(configuration, frame) }
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,55 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RubyMemcheck
4
+ class TestTask < Rake::TestTask
5
+ VALGRIND_REPORT_MSG = "Valgrind reported errors (e.g. memory leak or use-after-free)"
6
+
7
+ attr_reader :configuration, :errors
8
+
9
+ def initialize(*args)
10
+ @configuration =
11
+ if !args.empty? && args[0].is_a?(Configuration)
12
+ args.shift
13
+ else
14
+ RubyMemcheck.default_configuration
15
+ end
16
+
17
+ super
18
+ end
19
+
20
+ def ruby(*args, **options, &block)
21
+ command = configuration.command(args)
22
+ sh(command, **options) do |ok, res|
23
+ if ok && configuration.valgrind_xml_file
24
+ parse_valgrind_output
25
+ unless errors.empty?
26
+ output_valgrind_errors
27
+ raise VALGRIND_REPORT_MSG
28
+ end
29
+ end
30
+
31
+ yield ok, res if block_given?
32
+ end
33
+ end
34
+
35
+ private
36
+
37
+ def parse_valgrind_output
38
+ @errors = []
39
+
40
+ xml = Nokogiri::XML(configuration.valgrind_xml_file.read)
41
+ xml.xpath("/valgrindoutput/error").each do |error|
42
+ error = ValgrindError.new(configuration, error)
43
+ next if error.skip?
44
+ @errors << error
45
+ end
46
+ end
47
+
48
+ def output_valgrind_errors
49
+ @errors.each do |error|
50
+ configuration.output_io.puts error
51
+ configuration.output_io.puts
52
+ end
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,50 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RubyMemcheck
4
+ class ValgrindError
5
+ attr_reader :kind, :msg, :stack
6
+
7
+ def initialize(configuration, error)
8
+ @kind = error.at_xpath("kind").content
9
+ @msg =
10
+ if kind_leak?
11
+ error.at_xpath("xwhat/text").content
12
+ else
13
+ error.at_xpath("what").content
14
+ end
15
+ @stack = Stack.new(configuration, error.at_xpath("stack"))
16
+ @configuration = configuration
17
+ end
18
+
19
+ def skip?
20
+ if should_filter?
21
+ @configuration.skip_stack?(stack)
22
+ else
23
+ false
24
+ end
25
+ end
26
+
27
+ def to_s
28
+ str = StringIO.new
29
+ str << "#{msg}\n"
30
+ stack.frames.each do |frame|
31
+ str << if frame.in_binary?
32
+ " *#{frame}\n"
33
+ else
34
+ " #{frame}\n"
35
+ end
36
+ end
37
+ str.string
38
+ end
39
+
40
+ private
41
+
42
+ def should_filter?
43
+ kind_leak?
44
+ end
45
+
46
+ def kind_leak?
47
+ kind.start_with?("Leak_")
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RubyMemcheck
4
+ VERSION = "0.1.0"
5
+ end
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "tempfile"
4
+ require "nokogiri"
5
+ require "rake/testtask"
6
+
7
+ require "ruby_memcheck/configuration"
8
+ require "ruby_memcheck/frame"
9
+ require "ruby_memcheck/stack"
10
+ require "ruby_memcheck/test_task"
11
+ require "ruby_memcheck/valgrind_error"
12
+ require "ruby_memcheck/version"
13
+
14
+ module RubyMemcheck
15
+ class << self
16
+ def config(**opts)
17
+ @default_configuration = Configuration.new(**opts)
18
+ end
19
+
20
+ def default_configuration
21
+ unless @default_configuration
22
+ raise "RubyMemcheck is not configured with a default configuration. "\
23
+ "Please run RubyMemcheck.config before using it."
24
+ end
25
+ @default_configuration
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "lib/ruby_memcheck/version"
4
+
5
+ Gem::Specification.new do |spec|
6
+ spec.name = "ruby_memcheck"
7
+ spec.version = RubyMemcheck::VERSION
8
+ spec.authors = ["Peter Zhu"]
9
+ spec.email = ["peter@peterzhu.ca"]
10
+
11
+ spec.summary = "Use Valgrind memcheck without going crazy"
12
+ spec.homepage = "https://github.com/peterzhu2118/ruby_memcheck"
13
+ spec.license = "MIT"
14
+ spec.required_ruby_version = Gem::Requirement.new(">= 2.3.0")
15
+
16
+ spec.metadata["homepage_uri"] = spec.homepage
17
+
18
+ # Specify which files should be added to the gem when it is released.
19
+ # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
20
+ spec.files = Dir.chdir(File.expand_path("..", __FILE__)) do
21
+ %x(git ls-files -z).split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
22
+ end
23
+ spec.bindir = "exe"
24
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
25
+ spec.require_paths = ["lib"]
26
+
27
+ spec.add_dependency("nokogiri")
28
+
29
+ spec.add_development_dependency("minitest", "~> 5.0")
30
+ spec.add_development_dependency("rake", "~> 13.0")
31
+ spec.add_development_dependency("rake-compiler", "~> 1.1")
32
+ spec.add_development_dependency("rubocop", "~> 1.22")
33
+ spec.add_development_dependency("rubocop-shopify", "~> 2.3")
34
+ end
metadata ADDED
@@ -0,0 +1,144 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: ruby_memcheck
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Peter Zhu
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2021-10-15 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: nokogiri
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: minitest
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '5.0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '5.0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rake
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '13.0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '13.0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: rake-compiler
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '1.1'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '1.1'
69
+ - !ruby/object:Gem::Dependency
70
+ name: rubocop
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '1.22'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '1.22'
83
+ - !ruby/object:Gem::Dependency
84
+ name: rubocop-shopify
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - "~>"
88
+ - !ruby/object:Gem::Version
89
+ version: '2.3'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - "~>"
95
+ - !ruby/object:Gem::Version
96
+ version: '2.3'
97
+ description:
98
+ email:
99
+ - peter@peterzhu.ca
100
+ executables: []
101
+ extensions: []
102
+ extra_rdoc_files: []
103
+ files:
104
+ - ".github/workflows/lint.yml"
105
+ - ".github/workflows/test.yml"
106
+ - ".gitignore"
107
+ - ".rubocop.yml"
108
+ - Gemfile
109
+ - LICENSE.txt
110
+ - README.md
111
+ - Rakefile
112
+ - lib/ruby_memcheck.rb
113
+ - lib/ruby_memcheck/configuration.rb
114
+ - lib/ruby_memcheck/frame.rb
115
+ - lib/ruby_memcheck/stack.rb
116
+ - lib/ruby_memcheck/test_task.rb
117
+ - lib/ruby_memcheck/valgrind_error.rb
118
+ - lib/ruby_memcheck/version.rb
119
+ - ruby_memcheck.gemspec
120
+ homepage: https://github.com/peterzhu2118/ruby_memcheck
121
+ licenses:
122
+ - MIT
123
+ metadata:
124
+ homepage_uri: https://github.com/peterzhu2118/ruby_memcheck
125
+ post_install_message:
126
+ rdoc_options: []
127
+ require_paths:
128
+ - lib
129
+ required_ruby_version: !ruby/object:Gem::Requirement
130
+ requirements:
131
+ - - ">="
132
+ - !ruby/object:Gem::Version
133
+ version: 2.3.0
134
+ required_rubygems_version: !ruby/object:Gem::Requirement
135
+ requirements:
136
+ - - ">="
137
+ - !ruby/object:Gem::Version
138
+ version: '0'
139
+ requirements: []
140
+ rubygems_version: 3.2.15
141
+ signing_key:
142
+ specification_version: 4
143
+ summary: Use Valgrind memcheck without going crazy
144
+ test_files: []