ruby_tracer 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: 752f406d1a3b8967596a30cc918fe2176f0da30df84651316ce452fae36cc940
4
+ data.tar.gz: cc21e0f38a5508be8154621d7a68a247a2cc1a43d0fc9c3294176bb32ad079ca
5
+ SHA512:
6
+ metadata.gz: ef97f505b367ef51efe01877912697208f42be67e5d531515c176aa9b2e6c14a0c795a5707b075d3a9f4b4051a96257220c905c0437bfc64f7e99a01deb942aa
7
+ data.tar.gz: 5162367688b0581d28c63219f6a38953eff903fcbe9473b22001edb8670ad1f945c89308f9833d7684d7b331457dc35d25e3ff6cdb4cef4d34defccee1d0ef5e
data/CHANGELOG.md ADDED
@@ -0,0 +1,5 @@
1
+ ## [Unreleased]
2
+
3
+ ## [0.1.0] - 2023-02-07
4
+
5
+ - Initial release
@@ -0,0 +1,84 @@
1
+ # Contributor Covenant Code of Conduct
2
+
3
+ ## Our Pledge
4
+
5
+ We as members, contributors, and leaders pledge to make participation in our community a harassment-free experience for everyone, regardless of age, body size, visible or invisible disability, ethnicity, sex characteristics, gender identity and expression, level of experience, education, socio-economic status, nationality, personal appearance, race, religion, or sexual identity and orientation.
6
+
7
+ We pledge to act and interact in ways that contribute to an open, welcoming, diverse, inclusive, and healthy community.
8
+
9
+ ## Our Standards
10
+
11
+ Examples of behavior that contributes to a positive environment for our community include:
12
+
13
+ * Demonstrating empathy and kindness toward other people
14
+ * Being respectful of differing opinions, viewpoints, and experiences
15
+ * Giving and gracefully accepting constructive feedback
16
+ * Accepting responsibility and apologizing to those affected by our mistakes, and learning from the experience
17
+ * Focusing on what is best not just for us as individuals, but for the overall community
18
+
19
+ Examples of unacceptable behavior include:
20
+
21
+ * The use of sexualized language or imagery, and sexual attention or
22
+ advances of any kind
23
+ * Trolling, insulting or derogatory comments, and personal or political attacks
24
+ * Public or private harassment
25
+ * Publishing others' private information, such as a physical or email
26
+ address, without their explicit permission
27
+ * Other conduct which could reasonably be considered inappropriate in a
28
+ professional setting
29
+
30
+ ## Enforcement Responsibilities
31
+
32
+ Community leaders are responsible for clarifying and enforcing our standards of acceptable behavior and will take appropriate and fair corrective action in response to any behavior that they deem inappropriate, threatening, offensive, or harmful.
33
+
34
+ Community leaders have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, and will communicate reasons for moderation decisions when appropriate.
35
+
36
+ ## Scope
37
+
38
+ This Code of Conduct applies within all community spaces, and also applies when an individual is officially representing the community in public spaces. Examples of representing our community include using an official e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event.
39
+
40
+ ## Enforcement
41
+
42
+ Instances of abusive, harassing, or otherwise unacceptable behavior may be reported to the community leaders responsible for enforcement at stan.lo@shopify.com. All complaints will be reviewed and investigated promptly and fairly.
43
+
44
+ All community leaders are obligated to respect the privacy and security of the reporter of any incident.
45
+
46
+ ## Enforcement Guidelines
47
+
48
+ Community leaders will follow these Community Impact Guidelines in determining the consequences for any action they deem in violation of this Code of Conduct:
49
+
50
+ ### 1. Correction
51
+
52
+ **Community Impact**: Use of inappropriate language or other behavior deemed unprofessional or unwelcome in the community.
53
+
54
+ **Consequence**: A private, written warning from community leaders, providing clarity around the nature of the violation and an explanation of why the behavior was inappropriate. A public apology may be requested.
55
+
56
+ ### 2. Warning
57
+
58
+ **Community Impact**: A violation through a single incident or series of actions.
59
+
60
+ **Consequence**: A warning with consequences for continued behavior. No interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, for a specified period of time. This includes avoiding interactions in community spaces as well as external channels like social media. Violating these terms may lead to a temporary or permanent ban.
61
+
62
+ ### 3. Temporary Ban
63
+
64
+ **Community Impact**: A serious violation of community standards, including sustained inappropriate behavior.
65
+
66
+ **Consequence**: A temporary ban from any sort of interaction or public communication with the community for a specified period of time. No public or private interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, is allowed during this period. Violating these terms may lead to a permanent ban.
67
+
68
+ ### 4. Permanent Ban
69
+
70
+ **Community Impact**: Demonstrating a pattern of violation of community standards, including sustained inappropriate behavior, harassment of an individual, or aggression toward or disparagement of classes of individuals.
71
+
72
+ **Consequence**: A permanent ban from any sort of public interaction within the community.
73
+
74
+ ## Attribution
75
+
76
+ This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 2.0,
77
+ available at https://www.contributor-covenant.org/version/2/0/code_of_conduct.html.
78
+
79
+ Community Impact Guidelines were inspired by [Mozilla's code of conduct enforcement ladder](https://github.com/mozilla/diversity).
80
+
81
+ [homepage]: https://www.contributor-covenant.org
82
+
83
+ For answers to common questions about this code of conduct, see the FAQ at
84
+ https://www.contributor-covenant.org/faq. Translations are available at https://www.contributor-covenant.org/translations.
data/Gemfile ADDED
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ source "https://rubygems.org"
4
+
5
+ # Specify your gem's dependencies in ruby-tracer.gemspec
6
+ gemspec
7
+
8
+ gem "rake", "~> 13.0"
9
+
10
+ gem "test-unit", "~> 3.0"
11
+
12
+ gem "ruby-lsp"
data/Gemfile.lock ADDED
@@ -0,0 +1,34 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ ruby_tracer (0.1.0)
5
+
6
+ GEM
7
+ remote: https://rubygems.org/
8
+ specs:
9
+ language_server-protocol (3.17.0.3)
10
+ power_assert (2.0.2)
11
+ prettier_print (1.2.0)
12
+ rake (13.0.6)
13
+ ruby-lsp (0.3.8)
14
+ language_server-protocol (~> 3.17.0)
15
+ sorbet-runtime
16
+ syntax_tree (>= 5.0.0, < 6)
17
+ sorbet-runtime (0.5.10648)
18
+ syntax_tree (5.3.0)
19
+ prettier_print (>= 1.2.0)
20
+ test-unit (3.5.5)
21
+ power_assert
22
+
23
+ PLATFORMS
24
+ arm64-darwin-21
25
+ x86_64-linux
26
+
27
+ DEPENDENCIES
28
+ rake (~> 13.0)
29
+ ruby-lsp
30
+ ruby_tracer!
31
+ test-unit (~> 3.0)
32
+
33
+ BUNDLED WITH
34
+ 2.4.2
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2023 Stan Lo
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,143 @@
1
+ # ruby_tracer
2
+
3
+ ruby_tracer is an extraction of [`ruby/debug`](https://github.com/ruby/debug)'s [powerful tracers](https://github.com/ruby/debug/blob/master/lib/debug/tracer.rb), with user-facing APIs and some improvements on accuracy.
4
+
5
+ Its goal is to help users understand their Ruby programss activities by emitting useful trace information, such us:
6
+
7
+ - How and where is the target object is being used (`ObjectTracer`)
8
+ - What exceptions are raised during the execution (`ExceptionTracer`)
9
+ - When method calls are being performed (`CallTracer`)
10
+ - Line execution (`LineTracer`)
11
+
12
+
13
+ ## Installation
14
+
15
+ ```shell
16
+ $ bundle add ruby_tracer --group=development,test
17
+ ```
18
+
19
+ If bundler is not being used to manage dependencies, install the gem by executing:
20
+
21
+ ```shell
22
+ $ gem install ruby_tracer
23
+ ```
24
+
25
+ ## Usage
26
+
27
+ ### ObjectTracer
28
+
29
+ ```rb
30
+ class User
31
+ def initialize(name) = (@name = name)
32
+
33
+ def name() = @name
34
+ end
35
+
36
+ def authorized?(user)
37
+ user.name == "John"
38
+ end
39
+
40
+ user = User.new("John")
41
+ tracer = ObjectTracer.new(user)
42
+ tracer.start do
43
+ user.name
44
+ authorized?(user)
45
+ end
46
+
47
+ #depth:3 #<User:0x000000010696cad8 @name="John"> receives #name (User#name) at test.rb:14:in `block in <main>'
48
+ #depth:3 #<User:0x000000010696cad8 @name="John"> is used as a parameter user of Object#authorized? at test.rb:15:in `block in <main>'
49
+ #depth:4 #<User:0x000000010696cad8 @name="John"> receives #name (User#name) at test.rb:8:in `authorized?'
50
+ ```
51
+
52
+ ### ExceptionTracer
53
+
54
+ ```rb
55
+ ExceptionTracer.new.start
56
+
57
+ begin
58
+ raise "boom"
59
+ rescue StandardError
60
+ nil
61
+ end
62
+
63
+ #depth:1 #<RuntimeError: boom> at test.rb:4
64
+ ```
65
+
66
+ ### CallTracer
67
+
68
+ ```rb
69
+ class User
70
+ def initialize(name) = (@name = name)
71
+
72
+ def name() = @name
73
+ end
74
+
75
+ def authorized?(user)
76
+ user.name == "John"
77
+ end
78
+
79
+ user = User.new("John")
80
+ tracer = CallTracer.new
81
+ tracer.start do
82
+ user.name
83
+ authorized?(user)
84
+ end
85
+
86
+ #depth:4 > block at test.rb:13
87
+ #depth:5 > User#name at test.rb:4
88
+ #depth:5 < User#name #=> "John" at test.rb:4
89
+ #depth:5 > Object#authorized? at test.rb:7
90
+ #depth:6 > User#name at test.rb:4
91
+ #depth:6 < User#name #=> "John" at test.rb:4
92
+ #depth:6 > String#== at test.rb:8
93
+ #depth:6 < String#== #=> true at test.rb:8
94
+ #depth:5 < Object#authorized? #=> true at test.rb:9
95
+ #depth:4 < block #=> true at test.rb:16
96
+ ```
97
+
98
+ ### LineTracer
99
+
100
+ ```rb
101
+ class User
102
+ def initialize(name) = (@name = name)
103
+
104
+ def name() = @name
105
+ end
106
+
107
+ def authorized?(user)
108
+ user.name == "John"
109
+ end
110
+
111
+ user = User.new("John")
112
+ tracer = LineTracer.new
113
+ tracer.start do
114
+ user.name
115
+ authorized?(user)
116
+ end
117
+
118
+ #depth:4 at test.rb:14
119
+ #depth:4 at test.rb:15
120
+ #depth:5 at test.rb:8
121
+ ```
122
+
123
+ ## Acknowledgement
124
+
125
+ [@ko1](https://github.com/ko1) (Koichi Sasada) implemented the majority of [`ruby/debug`](https://github.com/ruby/debug), including its tracers. So this project wouldn't exist without his amazing work there.
126
+
127
+ ## Development
128
+
129
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake test-unit` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
130
+
131
+ To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and the created tag, and push the `.gem` file to [rubygems.org](https://rubygems.org).
132
+
133
+ ## Contributing
134
+
135
+ Bug reports and pull requests are welcome on GitHub at https://github.com/st0012/ruby_tracer. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [code of conduct](https://github.com/st0012/ruby_tracer/blob/master/CODE_OF_CONDUCT.md).
136
+
137
+ ## License
138
+
139
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
140
+
141
+ ## Code of Conduct
142
+
143
+ Everyone interacting in the Ruby::Tracer project's codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/st0012/ruby_tracer/blob/master/CODE_OF_CONDUCT.md).
data/Rakefile ADDED
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "bundler/gem_tasks"
4
+ require "rake/testtask"
5
+
6
+ Rake::TestTask.new(:test) do |t|
7
+ t.libs << "test"
8
+ t.libs << "lib"
9
+ t.test_files = FileList["test/**/*_test.rb"]
10
+ end
11
+
12
+ task default: :test
@@ -0,0 +1,171 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "pp"
4
+ require_relative "color"
5
+
6
+ module Tracer
7
+ class Base
8
+ DIR = __dir__
9
+ M_OBJECT_ID = Object.instance_method(:object_id)
10
+ M_INSPECT = Object.instance_method(:inspect)
11
+ M_CLASS = Object.instance_method(:class)
12
+ M_IS_A = Object.instance_method(:is_a?)
13
+ HOME = ENV["HOME"] ? (ENV["HOME"] + "/") : nil
14
+
15
+ include Color
16
+
17
+ class LimitedPP
18
+ def self.pp(obj, max)
19
+ out = self.new(max)
20
+ catch out do
21
+ PP.singleline_pp(obj, out)
22
+ end
23
+ out.buf
24
+ end
25
+
26
+ attr_reader :buf
27
+
28
+ def initialize(max)
29
+ @max = max
30
+ @cnt = 0
31
+ @buf = String.new
32
+ end
33
+
34
+ def <<(other)
35
+ @buf << other
36
+
37
+ if @buf.size >= @max
38
+ @buf = @buf[0..@max] + "..."
39
+ throw self
40
+ end
41
+ end
42
+ end
43
+
44
+ def safe_inspect(obj, max_length: 40)
45
+ LimitedPP.pp(obj, max_length)
46
+ rescue NoMethodError => e
47
+ klass, oid = M_CLASS.bind_call(obj), M_OBJECT_ID.bind_call(obj)
48
+ if obj == (r = e.receiver)
49
+ "#<#{klass.name}#{oid} does not have \#inspect>"
50
+ else
51
+ rklass, roid = M_CLASS.bind_call(r), M_OBJECT_ID.bind_call(r)
52
+ "#<#{klass.name}:#{roid} contains #<#{rklass}:#{roid} and it does not have #inspect>"
53
+ end
54
+ rescue Exception => e
55
+ "<#inspect raises #{e.inspect}>"
56
+ end
57
+
58
+ def pretty_path(path)
59
+ return "#<none>" unless path
60
+
61
+ case
62
+ when path.start_with?(dir = RbConfig::CONFIG["rubylibdir"] + "/")
63
+ path.sub(dir, "$(rubylibdir)/")
64
+ when Gem.path.any? { |gp| path.start_with?(dir = gp + "/gems/") }
65
+ path.sub(dir, "$(Gem)/")
66
+ when HOME && path.start_with?(HOME)
67
+ path.sub(HOME, "~/")
68
+ else
69
+ path
70
+ end
71
+ end
72
+
73
+ def initialize(output: STDOUT, pattern: nil, colorize: nil)
74
+ @name = self.class.name
75
+ @type = @name.sub(/Tracer\z/, "")
76
+ @output = output
77
+ @colorize = colorize || colorizable?
78
+
79
+ if pattern
80
+ @pattern = Regexp.compile(pattern)
81
+ else
82
+ @pattern = nil
83
+ end
84
+
85
+ @tp = setup_tp
86
+ end
87
+
88
+ def key
89
+ [@type, @pattern, @into].freeze
90
+ end
91
+
92
+ def header
93
+ ""
94
+ end
95
+
96
+ def to_s
97
+ s = "#{@name} #{description}"
98
+ s += " with pattern #{@pattern.inspect}" if @pattern
99
+ s
100
+ end
101
+
102
+ def description
103
+ "(#{@tp.enabled? ? "enabled" : "disabled"})"
104
+ end
105
+
106
+ def start(&block)
107
+ puts "PID:#{Process.pid} #{self}" if @output.is_a?(File)
108
+
109
+ if block
110
+ @tp.enable(&block)
111
+ else
112
+ @tp.enable
113
+ self
114
+ end
115
+ end
116
+
117
+ def stop
118
+ @tp.disable
119
+ end
120
+
121
+ def started?
122
+ @tp.enabled?
123
+ end
124
+
125
+ def stopped?
126
+ !started?
127
+ end
128
+
129
+ def skip?(tp)
130
+ skip_internal?(tp) || skip_with_pattern?(tp)
131
+ end
132
+
133
+ def skip_with_pattern?(tp)
134
+ @pattern && !tp.path.match?(@pattern)
135
+ end
136
+
137
+ def skip_internal?(tp)
138
+ tp.path.match?(DIR)
139
+ end
140
+
141
+ def out(tp, msg = nil, depth: caller.size - 1, location: nil)
142
+ location ||= "#{tp.path}:#{tp.lineno}"
143
+ buff =
144
+ "#{header} \#depth:#{"%-2d" % depth}#{msg} at #{colorize("#{location}", [:GREEN])}"
145
+
146
+ puts buff
147
+ end
148
+
149
+ def puts(msg)
150
+ @output.puts msg
151
+ @output.flush
152
+ end
153
+
154
+ def minfo(tp)
155
+ return "block{}" if tp.event == :b_call
156
+
157
+ klass = tp.defined_class
158
+
159
+ if klass.singleton_class?
160
+ "#{tp.self}.#{tp.method_id}"
161
+ else
162
+ "#{klass}\##{tp.method_id}"
163
+ end
164
+ end
165
+
166
+ def colorizable?
167
+ no_color = (nc = ENV["NO_COLOR"]).nil? || nc.empty?
168
+ @output.is_a?(IO) && @output.tty? && no_color
169
+ end
170
+ end
171
+ end
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "base"
4
+
5
+ class CallTracer < Tracer::Base
6
+ def setup_tp
7
+ TracePoint.new(:a_call, :a_return) do |tp|
8
+ next if skip?(tp)
9
+
10
+ depth = caller.size
11
+
12
+ call_identifier_str = (tp.defined_class ? minfo(tp) : "block")
13
+
14
+ call_identifier_str = colorize_blue(call_identifier_str)
15
+
16
+ case tp.event
17
+ when :call, :c_call, :b_call
18
+ depth += 1 if tp.event == :c_call
19
+ sp = " " * depth
20
+ out tp, ">#{sp}#{call_identifier_str}", depth: depth
21
+ when :return, :c_return, :b_return
22
+ depth += 1 if tp.event == :c_return
23
+ sp = " " * depth
24
+ return_str = colorize_magenta(safe_inspect(tp.return_value))
25
+ out tp, "<#{sp}#{call_identifier_str} #=> #{return_str}", depth: depth
26
+ end
27
+ end
28
+ end
29
+
30
+ def skip_with_pattern?(tp)
31
+ super && !tp.method_id&.match?(@pattern)
32
+ end
33
+ end
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Tracer
4
+ module Color
5
+ CLEAR = 0
6
+ BOLD = 1
7
+ UNDERLINE = 4
8
+ REVERSE = 7
9
+ RED = 31
10
+ GREEN = 32
11
+ YELLOW = 33
12
+ BLUE = 34
13
+ MAGENTA = 35
14
+ CYAN = 36
15
+
16
+ class << self
17
+ def colorize(text, seq)
18
+ seq = seq.map { |s| "\e[#{const_get(s)}m" }.join("")
19
+ "#{seq}#{text}#{clear}"
20
+ end
21
+
22
+ def clear
23
+ "\e[#{CLEAR}m"
24
+ end
25
+ end
26
+
27
+ def colorize(str, seq, colorize: @colorize)
28
+ !colorize ? str : Color.colorize(str, seq)
29
+ end
30
+
31
+ def colorize_cyan(str)
32
+ colorize(str, %i[CYAN BOLD])
33
+ end
34
+
35
+ def colorize_blue(str)
36
+ colorize(str, %i[BLUE BOLD])
37
+ end
38
+
39
+ def colorize_magenta(str)
40
+ colorize(str, %i[MAGENTA BOLD])
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "base"
4
+
5
+ class ExceptionTracer < Tracer::Base
6
+ def setup_tp
7
+ TracePoint.new(:raise) do |tp|
8
+ next if skip?(tp)
9
+
10
+ exc = tp.raised_exception
11
+
12
+ out tp, " #{colorize_magenta(exc.inspect)}"
13
+ rescue Exception => e
14
+ p e
15
+ end
16
+ end
17
+
18
+ def skip_with_pattern?(tp)
19
+ super && !tp.raised_exception.inspect.match?(@pattern)
20
+ end
21
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "base"
4
+
5
+ class LineTracer < Tracer::Base
6
+ def setup_tp
7
+ TracePoint.new(:line) do |tp|
8
+ next if skip?(tp)
9
+ # pp tp.object_id, caller(0)
10
+ out tp
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,117 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "base"
4
+
5
+ class ObjectTracer < Tracer::Base
6
+ attr_reader :target_id, :target_label
7
+
8
+ def initialize(target = nil, target_id: nil, target_label: nil, **kw)
9
+ unless target || target_id
10
+ raise ArgumentError, "target or target_id is required"
11
+ end
12
+
13
+ @target_id = target_id || M_OBJECT_ID.bind_call(target)
14
+ @target_label =
15
+ (target ? safe_inspect(target) : target_label || "<unlabelled>")
16
+ super(**kw)
17
+ end
18
+
19
+ def key
20
+ [@type, @target_id, @pattern, @into].freeze
21
+ end
22
+
23
+ def description
24
+ "for #{@target_label} #{super}"
25
+ end
26
+
27
+ def colorized_target_label
28
+ colorize_magenta(@target_label)
29
+ end
30
+
31
+ PRIMITIVE_METHOD_SOURCES = [Module, Class, Object, Kernel]
32
+
33
+ def setup_tp
34
+ TracePoint.new(:a_call) do |tp|
35
+ next if skip?(tp)
36
+
37
+ if M_OBJECT_ID.bind_call(tp.self) == @target_id
38
+ if PRIMITIVE_METHOD_SOURCES.any? { |klass| klass == tp.defined_class }
39
+ next
40
+ end
41
+
42
+ internal_depth = 2
43
+ klass = tp.defined_class
44
+ method = tp.method_id
45
+ method_info =
46
+ method_info =
47
+ if klass
48
+ if klass.singleton_class?
49
+ if M_IS_A.bind_call(tp.self, Class)
50
+ ".#{method} (#{klass}.#{method})"
51
+ else
52
+ ".#{method}"
53
+ end
54
+ else
55
+ "##{method} (#{klass}##{method})"
56
+ end
57
+ else
58
+ if method
59
+ "##{method} (<unknown>##{method})"
60
+ else
61
+ "<eval or exec with &block>"
62
+ end
63
+ end
64
+
65
+ out tp,
66
+ " #{colorized_target_label} receives #{colorize_blue(method_info)}",
67
+ location: caller_locations(internal_depth, 1).first,
68
+ depth: caller.size - internal_depth
69
+ elsif !tp.parameters.empty?
70
+ b = tp.binding
71
+ method_info = colorize_blue(minfo(tp))
72
+
73
+ tp.parameters.each do |type, name|
74
+ next unless name
75
+
76
+ colorized_name = colorize_cyan(name)
77
+
78
+ case type
79
+ when :req, :opt, :key, :keyreq
80
+ if M_OBJECT_ID.bind_call(b.local_variable_get(name)) == @target_id
81
+ internal_depth = 4
82
+ out tp,
83
+ " #{colorized_target_label} is used as a parameter #{colorized_name} of #{method_info}",
84
+ location: caller_locations(internal_depth, 1).first,
85
+ depth: caller.size - internal_depth
86
+ end
87
+ when :rest
88
+ next if name == :"*"
89
+
90
+ internal_depth = 6
91
+ ary = b.local_variable_get(name)
92
+ ary.each do |e|
93
+ if M_OBJECT_ID.bind_call(e) == @target_id
94
+ out tp,
95
+ " #{colorized_target_label} is used as a parameter in #{colorized_name} of #{method_info}",
96
+ location: caller_locations(internal_depth, 1).first,
97
+ depth: caller.size - internal_depth
98
+ end
99
+ end
100
+ when :keyrest
101
+ next if name == :"**"
102
+ internal_depth = 6
103
+ h = b.local_variable_get(name)
104
+ h.each do |k, e|
105
+ if M_OBJECT_ID.bind_call(e) == @target_id
106
+ out tp,
107
+ " #{colorized_target_label} is used as a parameter in #{colorized_name} of #{method_info}",
108
+ location: caller_locations(internal_depth, 1).first,
109
+ depth: caller.size - internal_depth
110
+ end
111
+ end
112
+ end
113
+ end
114
+ end
115
+ end
116
+ end
117
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Tracer
4
+ VERSION = "0.1.0"
5
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "ruby_tracer/version"
4
+ require_relative "ruby_tracer/line_tracer"
5
+ require_relative "ruby_tracer/call_tracer"
6
+ require_relative "ruby_tracer/exception_tracer"
7
+ require_relative "ruby_tracer/object_tracer"
metadata ADDED
@@ -0,0 +1,61 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: ruby_tracer
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Stan Lo
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2023-02-10 00:00:00.000000000 Z
12
+ dependencies: []
13
+ description: A Ruby tracer
14
+ email:
15
+ - stan001212@gmail.com
16
+ executables: []
17
+ extensions: []
18
+ extra_rdoc_files: []
19
+ files:
20
+ - CHANGELOG.md
21
+ - CODE_OF_CONDUCT.md
22
+ - Gemfile
23
+ - Gemfile.lock
24
+ - LICENSE.txt
25
+ - README.md
26
+ - Rakefile
27
+ - lib/ruby_tracer.rb
28
+ - lib/ruby_tracer/base.rb
29
+ - lib/ruby_tracer/call_tracer.rb
30
+ - lib/ruby_tracer/color.rb
31
+ - lib/ruby_tracer/exception_tracer.rb
32
+ - lib/ruby_tracer/line_tracer.rb
33
+ - lib/ruby_tracer/object_tracer.rb
34
+ - lib/ruby_tracer/version.rb
35
+ homepage: https://github.com/st0012/ruby_tracer
36
+ licenses:
37
+ - MIT
38
+ metadata:
39
+ homepage_uri: https://github.com/st0012/ruby_tracer
40
+ source_code_uri: https://github.com/st0012/ruby_tracer
41
+ changelog_uri: https://github.com/st0012/ruby_tracer/blob/main/CHANGELOG.md
42
+ post_install_message:
43
+ rdoc_options: []
44
+ require_paths:
45
+ - lib
46
+ required_ruby_version: !ruby/object:Gem::Requirement
47
+ requirements:
48
+ - - ">="
49
+ - !ruby/object:Gem::Version
50
+ version: 2.7.0
51
+ required_rubygems_version: !ruby/object:Gem::Requirement
52
+ requirements:
53
+ - - ">="
54
+ - !ruby/object:Gem::Version
55
+ version: '0'
56
+ requirements: []
57
+ rubygems_version: 3.4.1
58
+ signing_key:
59
+ specification_version: 4
60
+ summary: A Ruby tracer
61
+ test_files: []