google-cloud-debugger 0.24.0

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.
Files changed (47) hide show
  1. checksums.yaml +7 -0
  2. data/.yardopts +8 -0
  3. data/LICENSE +201 -0
  4. data/README.md +56 -0
  5. data/ext/google/cloud/debugger/debugger_c/debugger.c +31 -0
  6. data/ext/google/cloud/debugger/debugger_c/debugger.h +26 -0
  7. data/ext/google/cloud/debugger/debugger_c/evaluator.c +78 -0
  8. data/ext/google/cloud/debugger/debugger_c/evaluator.h +25 -0
  9. data/ext/google/cloud/debugger/debugger_c/extconf.rb +22 -0
  10. data/ext/google/cloud/debugger/debugger_c/tracer.c +478 -0
  11. data/ext/google/cloud/debugger/debugger_c/tracer.h +31 -0
  12. data/lib/google-cloud-debugger.rb +121 -0
  13. data/lib/google/cloud/debugger.rb +379 -0
  14. data/lib/google/cloud/debugger/agent.rb +204 -0
  15. data/lib/google/cloud/debugger/async_actor.rb +290 -0
  16. data/lib/google/cloud/debugger/breakpoint.rb +382 -0
  17. data/lib/google/cloud/debugger/breakpoint/evaluator.rb +1113 -0
  18. data/lib/google/cloud/debugger/breakpoint/source_location.rb +75 -0
  19. data/lib/google/cloud/debugger/breakpoint/stack_frame.rb +109 -0
  20. data/lib/google/cloud/debugger/breakpoint/variable.rb +304 -0
  21. data/lib/google/cloud/debugger/breakpoint_manager.rb +217 -0
  22. data/lib/google/cloud/debugger/credentials.rb +41 -0
  23. data/lib/google/cloud/debugger/debuggee.rb +204 -0
  24. data/lib/google/cloud/debugger/debuggee/app_uniquifier_generator.rb +78 -0
  25. data/lib/google/cloud/debugger/middleware.rb +77 -0
  26. data/lib/google/cloud/debugger/project.rb +135 -0
  27. data/lib/google/cloud/debugger/rails.rb +141 -0
  28. data/lib/google/cloud/debugger/service.rb +130 -0
  29. data/lib/google/cloud/debugger/tracer.rb +165 -0
  30. data/lib/google/cloud/debugger/transmitter.rb +129 -0
  31. data/lib/google/cloud/debugger/v2.rb +15 -0
  32. data/lib/google/cloud/debugger/v2/controller2_client.rb +299 -0
  33. data/lib/google/cloud/debugger/v2/controller2_client_config.json +43 -0
  34. data/lib/google/cloud/debugger/v2/debugger2_client.rb +378 -0
  35. data/lib/google/cloud/debugger/v2/debugger2_client_config.json +53 -0
  36. data/lib/google/cloud/debugger/v2/doc/google/devtools/clouddebugger/v2/data.rb +441 -0
  37. data/lib/google/cloud/debugger/v2/doc/google/devtools/clouddebugger/v2/debugger.rb +151 -0
  38. data/lib/google/cloud/debugger/v2/doc/google/devtools/source/v1/source_context.rb +161 -0
  39. data/lib/google/cloud/debugger/v2/doc/google/protobuf/timestamp.rb +81 -0
  40. data/lib/google/cloud/debugger/version.rb +22 -0
  41. data/lib/google/devtools/clouddebugger/v2/controller_pb.rb +47 -0
  42. data/lib/google/devtools/clouddebugger/v2/controller_services_pb.rb +97 -0
  43. data/lib/google/devtools/clouddebugger/v2/data_pb.rb +105 -0
  44. data/lib/google/devtools/clouddebugger/v2/debugger_pb.rb +74 -0
  45. data/lib/google/devtools/clouddebugger/v2/debugger_services_pb.rb +64 -0
  46. data/lib/google/devtools/source/v1/source_context_pb.rb +89 -0
  47. metadata +300 -0
@@ -0,0 +1,382 @@
1
+ # Copyright 2017 Google Inc. All rights reserved.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+
16
+ require "time"
17
+ require "google/cloud/debugger/breakpoint/evaluator"
18
+ require "google/cloud/debugger/breakpoint/source_location"
19
+ require "google/cloud/debugger/breakpoint/stack_frame"
20
+ require "google/cloud/debugger/breakpoint/variable"
21
+
22
+ module Google
23
+ module Cloud
24
+ module Debugger
25
+ class Breakpoint
26
+ include MonitorMixin
27
+
28
+ ##
29
+ # Breakpoint identifier, unique in the scope of the debuggee.
30
+ attr_accessor :id
31
+
32
+ # TODO: Implement logpoint
33
+ # attr_accessor :action
34
+ # attr_accessor :log_message_format
35
+ # attr_accessor :log_level
36
+
37
+ ##
38
+ # Breakpoint source location.
39
+ # @return [Google::Cloud::Debugger::Breakpoint::SourceLocation]
40
+ attr_accessor :location
41
+
42
+ ##
43
+ # Condition that triggers the breakpoint. The condition is a compound
44
+ # boolean expression composed using expressions in a programming
45
+ # language at the source location.
46
+ attr_accessor :condition
47
+
48
+ ##
49
+ # When true, indicates that this is a final result and the breakpoint
50
+ # state will not change from here on.
51
+ # @return [Boolean]
52
+ attr_accessor :is_final_state
53
+
54
+ ##
55
+ # List of read-only expressions to evaluate at the breakpoint location.
56
+ # The expressions are composed using expressions in the programming
57
+ # language at the source location. If the breakpoint action is LOG, the
58
+ # evaluated expressions are included in log statements.
59
+ # @return [Array<String>]
60
+ attr_accessor :expressions
61
+
62
+ ##
63
+ # Values of evaluated expressions at breakpoint time. The evaluated
64
+ # expressions appear in exactly the same order they are listed in the
65
+ # expressions field. The name field holds the original expression text,
66
+ # the value or members field holds the result of the evaluated
67
+ # expression. If the expression cannot be evaluated, the status inside
68
+ # the Variable will indicate an error and contain the error text.
69
+ # @return [Array<Google::Cloud::Debugger::Breakpoint::Variable>]
70
+ attr_accessor :evaluated_expressions
71
+
72
+ ##
73
+ # Time this breakpoint was created by the server in seconds resolution.
74
+ # @return [Time]
75
+ attr_accessor :create_time
76
+
77
+ ##
78
+ # Time this breakpoint was finalized as seen by the server in seconds
79
+ # resolution.
80
+ # @return [Time]
81
+ attr_accessor :final_time
82
+
83
+ ##
84
+ # E-mail address of the user that created this breakpoint
85
+ attr_accessor :user_email
86
+
87
+ ##
88
+ # Breakpoint status.
89
+ #
90
+ # The status includes an error flag and a human readable message. This
91
+ # field is usually unset. The message can be either informational or an
92
+ # error message. Regardless, clients should always display the text
93
+ # message back to the user.
94
+ #
95
+ # Error status indicates complete failure of the breakpoint.
96
+ attr_accessor :status
97
+
98
+ ##
99
+ # The variable_table exists to aid with computation, memory and network
100
+ # traffic optimization. It enables storing a variable once and reference
101
+ # it from multiple variables, including variables stored in the
102
+ # variable_table itself. For example, the same this object, which may
103
+ # appear at many levels of the stack, can have all of its data stored
104
+ # once in this table. The stack frame variables then would hold only a
105
+ # reference to it.
106
+ #
107
+ # The variable var_table_index field is an index into this repeated
108
+ # field. The stored objects are nameless and get their name from the
109
+ # referencing variable. The effective variable is a merge of the
110
+ # referencing variable and the referenced variable.
111
+ # TODO: Implement variable table
112
+ # attr_accessor :variable_table
113
+
114
+ ##
115
+ # A set of custom breakpoint properties, populated by the agent, to be
116
+ # displayed to the user.
117
+ # @return [Hash<String, String>]
118
+ attr_accessor :labels
119
+
120
+ ##
121
+ # The stack at breakpoint time.
122
+ # @return [Array<Google::Cloud::Debugger::Breakpoint::StackFrame>]
123
+ attr_accessor :stack_frames
124
+
125
+ ##
126
+ # @private Construct a new instance of Breakpoint.
127
+ def initialize id = nil, path = nil, line = nil
128
+ super()
129
+
130
+ @id = id
131
+ # @action = :capture
132
+ @location = SourceLocation.new.tap do |sl|
133
+ sl.path = path
134
+ sl.line = line.to_i
135
+ end
136
+ @expressions = []
137
+ @evaluated_expressions = []
138
+ @stack_frames = []
139
+ @labels = {}
140
+ end
141
+
142
+ ##
143
+ # @private New Google::Cloud::Debugger::Breakpoint
144
+ # from a Google::Devtools::Clouddebugger::V2::Breakpoint object.
145
+ def self.from_grpc grpc
146
+ return new if grpc.nil?
147
+ new(grpc.id).tap do |b|
148
+ b.location = Breakpoint::SourceLocation.from_grpc grpc.location
149
+ b.condition = grpc.condition
150
+ b.is_final_state = grpc.is_final_state
151
+ b.expressions = grpc.expressions.to_a
152
+ b.evaluated_expressions =
153
+ Breakpoint::Variable.from_grpc_list grpc.evaluated_expressions
154
+ b.create_time = timestamp_from_grpc grpc.create_time
155
+ b.final_time = timestamp_from_grpc grpc.final_time
156
+ b.user_email = grpc.user_email
157
+ b.status = grpc.status
158
+ b.labels = hashify_labels grpc.labels
159
+ b.stack_frames = stack_frames_from_grpc grpc
160
+ end
161
+ end
162
+
163
+ ##
164
+ # @private Extract array of stack_frame from grpc
165
+ def self.stack_frames_from_grpc grpc
166
+ return nil if grpc.stack_frames.nil?
167
+ grpc.stack_frames.map { |sf| Breakpoint::StackFrame.from_grpc sf }
168
+ end
169
+
170
+ ##
171
+ # @private Get a Time object from a Google::Protobuf::Timestamp object.
172
+ def self.timestamp_from_grpc grpc_timestamp
173
+ return nil if grpc_timestamp.nil?
174
+ Time.at grpc_timestamp.seconds, Rational(grpc_timestamp.nanos, 1000)
175
+ end
176
+
177
+ ##
178
+ # @private Helper method to convert a gRPC map to Ruby Hash
179
+ def self.hashify_labels grpc_labels
180
+ if grpc_labels.respond_to? :to_h
181
+ grpc_labels.to_h
182
+ else
183
+ # Enumerable doesn't have to_h on ruby 2.0...
184
+ Hash[grpc_labels.to_a]
185
+ end
186
+ end
187
+
188
+ private_class_method :stack_frames_from_grpc, :timestamp_from_grpc,
189
+ :hashify_labels
190
+
191
+ ##
192
+ # Marks a breakpoint as complete if this breakpoint isn't completed
193
+ # already. Set @is_final_state to true and set @final_time.
194
+ def complete
195
+ synchronize do
196
+ return if complete?
197
+
198
+ @is_final_state = true
199
+ @final_time = Time.now
200
+ end
201
+ end
202
+
203
+ alias_method :complete?, :is_final_state
204
+
205
+ ##
206
+ # Get the file path of this breakpoint
207
+ # @example
208
+ # breakpoint = Breakpoint.new nil, "path/to/file.rb"
209
+ # breakpoint.path #=> "path/to/file.rb"
210
+ # @return [String] The file path for this breakpoint
211
+ def path
212
+ location.nil? ? nil : location.path
213
+ end
214
+
215
+ ##
216
+ # Get the line number of this breakpoint
217
+ # @example
218
+ # breakpoint = Breakpoint.new nil, "path/to/file.rb", 11
219
+ # breakpoint.line #=> 11
220
+ # @return [Integer] The line number for this breakpoint
221
+ def line
222
+ location.nil? ? nil : location.line
223
+ end
224
+
225
+ ##
226
+ # Evaluate the breakpoint's condition expression against a given binding
227
+ # object. Returns true if the condition expression evalutes to true;
228
+ # false if error happens or the expression evaluates to false.
229
+ #
230
+ # @param [Binding] binding A Ruby Binding object
231
+ # @return [Boolean] True if condition evalutes to true, false if
232
+ # condition evaluates to false or error raised during evaluation.
233
+ #
234
+ def check_condition binding
235
+ return true if condition.nil? || condition.empty?
236
+ begin
237
+ Evaluator.eval_condition binding, condition
238
+ rescue
239
+ set_error_state "Unable to evaluate condition",
240
+ refers_to: :BREAKPOINT_CONDITION
241
+ false
242
+ end
243
+ end
244
+
245
+ ##
246
+ # Evaluate the breakpoint unless it's already marked as completed. Use
247
+ # "@stack_frames" and "@evaluated_expressions" instance variables to
248
+ # store the result snapshot. Set breakpoint to complete when done.
249
+ #
250
+ # @param [Array<Binding>] call_stack_bindings An array of Ruby Binding
251
+ # objects, from the call stack that leads to the triggering of the
252
+ # breakpoints.
253
+ #
254
+ # @return [Boolean] True if evaluated successfully; false otherwise.
255
+ #
256
+ def eval_call_stack call_stack_bindings
257
+ synchronize do
258
+ return false if complete?
259
+
260
+ top_frame_binding = call_stack_bindings[0]
261
+
262
+ # Abort evaluation if breakpoint condition isn't met
263
+ return false unless check_condition top_frame_binding
264
+
265
+ begin
266
+ @stack_frames = Evaluator.eval_call_stack call_stack_bindings
267
+ unless expressions.empty?
268
+ @evaluated_expressions =
269
+ Evaluator.eval_expressions top_frame_binding, @expressions
270
+ end
271
+ rescue
272
+ return false
273
+ end
274
+
275
+ complete
276
+ end
277
+ true
278
+ end
279
+
280
+ ##
281
+ # Check if two breakpoints are equal to each other
282
+ def eql? other
283
+ id == other.id &&
284
+ path == other.path &&
285
+ line == other.line
286
+ end
287
+
288
+ ##
289
+ # @private Override default hashing function
290
+ def hash
291
+ id.hash ^ path.hash ^ line.hash
292
+ end
293
+
294
+ ##
295
+ # @private Exports the Breakpoint to a
296
+ # Google::Devtools::Clouddebugger::V2::Breakpoint object.
297
+ def to_grpc
298
+ Google::Devtools::Clouddebugger::V2::Breakpoint.new(
299
+ id: id.to_s,
300
+ location: location.to_grpc,
301
+ condition: condition.to_s,
302
+ expressions: expressions || [],
303
+ is_final_state: is_final_state,
304
+ create_time: timestamp_to_grpc(create_time),
305
+ final_time: timestamp_to_grpc(final_time),
306
+ user_email: user_email,
307
+ stack_frames: stack_frames_to_grpc,
308
+ evaluated_expressions: evaluated_expressions_to_grpc,
309
+ status: status,
310
+ labels: labels_to_grpc
311
+ )
312
+ end
313
+
314
+ ##
315
+ # Set breakpoint to an error state, which initializes the @status
316
+ # instance variable with the error message. Also mark this breakpoint as
317
+ # completed if is_final is true.
318
+ #
319
+ # @param [String] message The error message
320
+ # @param [Google::Devtools::Clouddebugger::V2::StatusMessage::Reference]
321
+ # refers_to Enum that specifies what the error refers to. Defaults
322
+ # :UNSPECIFIED.
323
+ # @param [Boolean] is_final Marks the breakpoint as final if true.
324
+ # Defaults true.
325
+ #
326
+ # @return [Google::Devtools::Clouddebugger::V2::StatusMessage] The grpc
327
+ # StatusMessage object, which describes the breakpoint's error state.
328
+ def set_error_state message, refers_to: :UNSPECIFIED, is_final: true
329
+ description = Google::Devtools::Clouddebugger::V2::FormatMessage.new(
330
+ format: message
331
+ )
332
+ @status = Google::Devtools::Clouddebugger::V2::StatusMessage.new(
333
+ is_error: true,
334
+ refers_to: refers_to,
335
+ description: description
336
+ )
337
+
338
+ complete if is_final
339
+
340
+ @status
341
+ end
342
+
343
+ private
344
+
345
+ ##
346
+ # @private Formats the labels so they can be saved to a
347
+ # Google::Devtools::Clouddebugger::V2::Breakpoint object.
348
+ def labels_to_grpc
349
+ # Coerce symbols to strings
350
+ Hash[labels.map do |k, v|
351
+ [String(k), String(v)]
352
+ end]
353
+ end
354
+
355
+ ##
356
+ # @private Exports the Breakpoint stack_frames to an array of
357
+ # Google::Devtools::Clouddebugger::V2::StackFrame objects.
358
+ def stack_frames_to_grpc
359
+ stack_frames.nil? ? [] : stack_frames.map(&:to_grpc)
360
+ end
361
+
362
+ ##
363
+ # @private Exports the Breakpoint stack_frames to an array of
364
+ # Google::Devtools::Clouddebugger::V2::StackFrame objects.
365
+ def evaluated_expressions_to_grpc
366
+ evaluated_expressions.nil? ? [] : evaluated_expressions.map(&:to_grpc)
367
+ end
368
+
369
+ ##
370
+ # @private Formats the timestamp as a Google::Protobuf::Timestamp
371
+ # object.
372
+ def timestamp_to_grpc time
373
+ return nil if time.nil?
374
+ Google::Protobuf::Timestamp.new(
375
+ seconds: time.to_i,
376
+ nanos: time.nsec
377
+ )
378
+ end
379
+ end
380
+ end
381
+ end
382
+ end
@@ -0,0 +1,1113 @@
1
+ # Copyright 2017 Google Inc. All rights reserved.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+
16
+ require "google/cloud/debugger/breakpoint/source_location"
17
+ require "google/cloud/debugger/breakpoint/stack_frame"
18
+ require "google/cloud/debugger/breakpoint/variable"
19
+
20
+ module Google
21
+ module Cloud
22
+ module Debugger
23
+ class Breakpoint
24
+ ##
25
+ # Helps to evaluate program state at the location of breakpoint during
26
+ # executing. The program state, such as local variables and call stack,
27
+ # are retrieved using Ruby Binding objects.
28
+ #
29
+ # The breakpoints may consist of conditional expression and other
30
+ # code expressions. The Evaluator helps evaluates these expression in
31
+ # a read-only context. Meaning if the expressions trigger any write
32
+ # operations in middle of the evaluation, the evaluator is able to
33
+ # abort the operation and prevent the program state from being altered.
34
+ #
35
+ # The evaluated results are saved onto the breakpoints fields. See
36
+ # [Stackdriver Breakpoints
37
+ # Doc](https://cloud.google.com/debugger/api/reference/rpc/google.devtools.clouddebugger.v2#google.devtools.clouddebugger.v2.Breakpoint)
38
+ # for details.
39
+ #
40
+ module Evaluator
41
+ ##
42
+ # Max number of top stacks to collect local variables information
43
+ STACK_EVAL_DEPTH = 5
44
+
45
+ ##
46
+ # @private YARV bytecode that the evaluator blocks during expression
47
+ # evaluation. If the breakpoint contains expressions that uses the
48
+ # following bytecode, the evaluator will block the expression
49
+ # evaluation from execusion.
50
+ BYTE_CODE_BLACKLIST = %w(
51
+ setinstancevariable
52
+ setclassvariable
53
+ setconstant
54
+ setglobal
55
+ defineclass
56
+ opt_ltlt
57
+ opt_aset
58
+ opt_aset_with
59
+ ).freeze
60
+
61
+ ##
62
+ # @private YARV bytecode that the evaluator blocks during expression
63
+ # evaluation on the top level. (not from within by predefined methods)
64
+ LOCAL_BYTE_CODE_BLACKLIST = %w(
65
+ setlocal
66
+ ).freeze
67
+
68
+ ##
69
+ # @private YARV bytecode call flags that the evaluator blocks during
70
+ # expression evaluation
71
+ FUNC_CALL_FLAG_BLACKLIST = %w(
72
+ ARGS_BLOCKARG
73
+ ).freeze
74
+
75
+ ##
76
+ # @private YARV instructions catch table type that the evaluator
77
+ # blocks during expression evaluation
78
+ CATCH_TABLE_TYPE_BLACKLIST = %w(
79
+ rescue
80
+ ).freeze
81
+
82
+ ##
83
+ # @private Predefined regex. Saves time during runtime.
84
+ BYTE_CODE_BLACKLIST_REGEX = /^\d+ #{BYTE_CODE_BLACKLIST.join '|'}/
85
+
86
+ ##
87
+ # @private Predefined regex. Saves time during runtime.
88
+ FULL_BYTE_CODE_BLACKLIST_REGEX = /^\d+ #{
89
+ [*BYTE_CODE_BLACKLIST, *LOCAL_BYTE_CODE_BLACKLIST].join '|'
90
+ }/
91
+
92
+ ##
93
+ # @private Predefined regex. Saves time during runtime.
94
+ FUNC_CALL_FLAG_BLACKLIST_REGEX =
95
+ /<callinfo!.+#{FUNC_CALL_FLAG_BLACKLIST.join '|'}/
96
+
97
+ ##
98
+ # @private Predefined regex. Saves time during runtime.
99
+ CATCH_TABLE_BLACKLIST_REGEX =
100
+ /catch table.*catch type: #{CATCH_TABLE_TYPE_BLACKLIST.join '|'}/m
101
+
102
+ private_constant :BYTE_CODE_BLACKLIST_REGEX,
103
+ :FULL_BYTE_CODE_BLACKLIST_REGEX,
104
+ :FUNC_CALL_FLAG_BLACKLIST_REGEX,
105
+ :CATCH_TABLE_BLACKLIST_REGEX
106
+
107
+ ##
108
+ # @private List of pre-approved classes to be used during expression
109
+ # evaluation.
110
+ IMMUTABLE_CLASSES = [
111
+ Complex,
112
+ FalseClass,
113
+ Float,
114
+ MatchData,
115
+ NilClass,
116
+ Numeric,
117
+ Proc,
118
+ Range,
119
+ Regexp,
120
+ Struct,
121
+ Symbol,
122
+ TrueClass,
123
+ Comparable,
124
+ Enumerable,
125
+ Math
126
+ ].concat(
127
+ RUBY_VERSION.to_f >= 2.4 ? [Integer] : [Bignum, Fixnum]
128
+ ).freeze
129
+
130
+ ##
131
+ # @private helper method to hashify an array
132
+ def self.hashify ary
133
+ ary.each.with_index(1).to_h
134
+ end
135
+ private_class_method :hashify
136
+
137
+ ##
138
+ # @private List of C level class methods that the evaluator allows
139
+ # during expression evaluation
140
+ C_CLASS_METHOD_WHITELIST = {
141
+ # Classes
142
+ ArgumentError => hashify(%I{
143
+ new
144
+ }).freeze,
145
+ Array => hashify(%I{
146
+ new
147
+ []
148
+ try_convert
149
+ }).freeze,
150
+ BasicObject => hashify(%I{
151
+ new
152
+ }).freeze,
153
+ Exception => hashify(%I{
154
+ exception
155
+ new
156
+ }).freeze,
157
+ Enumerator => hashify(%I{
158
+ new
159
+ }).freeze,
160
+ Fiber => hashify(%I{
161
+ current
162
+ }).freeze,
163
+ FiberError => hashify(%I{
164
+ new
165
+ }).freeze,
166
+ File => hashify(%I{
167
+ basename
168
+ dirname
169
+ extname
170
+ join
171
+ path
172
+ split
173
+ }).freeze,
174
+ FloatDomainError => hashify(%I{
175
+ new
176
+ }).freeze,
177
+ Hash => hashify(%I{
178
+ []
179
+ new
180
+ try_convert
181
+ }).freeze,
182
+ IndexError => hashify(%I{
183
+ new
184
+ }).freeze,
185
+ KeyError => hashify(%I{
186
+ new
187
+ }).freeze,
188
+ Module => hashify(%I{
189
+ constants
190
+ nesting
191
+ used_modules
192
+ }).freeze,
193
+ NameError => hashify(%I{
194
+ new
195
+ }).freeze,
196
+ NoMethodError => hashify(%I{
197
+ new
198
+ }).freeze,
199
+ Object => hashify(%I{
200
+ new
201
+ }).freeze,
202
+ RangeError => hashify(%I{
203
+ new
204
+ }).freeze,
205
+ RegexpError => hashify(%I{
206
+ new
207
+ }).freeze,
208
+ RuntimeError => hashify(%I{
209
+ new
210
+ }).freeze,
211
+ String => hashify(%I{
212
+ new
213
+ try_convert
214
+ }).freeze,
215
+ Thread => hashify(%I{
216
+ DEBUG
217
+ abort_on_exception
218
+ current
219
+ list
220
+ main
221
+ pending_interrupt?
222
+ report_on_exception
223
+ }).freeze,
224
+ Time => hashify(%I{
225
+ at
226
+ gm
227
+ local
228
+ mktime
229
+ new
230
+ now
231
+ utc
232
+ }).freeze,
233
+ TypeError => hashify(%I{
234
+ new
235
+ }).freeze,
236
+ Google::Cloud::Debugger::Breakpoint::Evaluator => hashify(%I{
237
+ disable_method_trace_for_thread
238
+ }).freeze,
239
+ ZeroDivisionError => hashify(%I{
240
+ new
241
+ }).freeze
242
+ }.freeze
243
+
244
+ ##
245
+ # @private List of C level instance methods that the evaluator allows
246
+ # during expression evaluation
247
+ C_INSTANCE_METHOD_WHITELIST = {
248
+ ArgumentError => hashify(%I{
249
+ initialize
250
+ }).freeze,
251
+ Array => hashify(%I{
252
+ initialize
253
+ &
254
+ *
255
+ +
256
+ -
257
+ <=>
258
+ ==
259
+ any?
260
+ assoc
261
+ at
262
+ bsearch
263
+ bsearch_index
264
+ collect
265
+ combination
266
+ compact
267
+ []
268
+ count
269
+ cycle
270
+ dig
271
+ drop
272
+ drop_while
273
+ each
274
+ each_index
275
+ empty?
276
+ eql?
277
+ fetch
278
+ find_index
279
+ first
280
+ flatten
281
+ frozen?
282
+ hash
283
+ include?
284
+ index
285
+ inspect
286
+ to_s
287
+ join
288
+ last
289
+ length
290
+ map
291
+ max
292
+ min
293
+ pack
294
+ permutation
295
+ product
296
+ rassoc
297
+ reject
298
+ repeated_combination
299
+ repeated_permutation
300
+ reverse
301
+ reverse_each
302
+ rindex
303
+ rotate
304
+ sample
305
+ select
306
+ shuffle
307
+ size
308
+ slice
309
+ sort
310
+ sum
311
+ take
312
+ take_while
313
+ to_a
314
+ to_ary
315
+ to_h
316
+ transpose
317
+ uniq
318
+ values_at
319
+ zip
320
+ |
321
+ }).freeze,
322
+ BasicObject => hashify(%I{
323
+ initialize
324
+ !
325
+ !=
326
+ ==
327
+ __id__
328
+ method_missing
329
+ object_id
330
+ send
331
+ __send__
332
+ equal?
333
+ }).freeze,
334
+ Binding => hashify(%I{
335
+ local_variable_defined?
336
+ local_variable_get
337
+ local_variables
338
+ receiver
339
+ }).freeze,
340
+ Class => hashify(%I{
341
+ superclass
342
+ }).freeze,
343
+ Dir => hashify(%I{
344
+ inspect
345
+ path
346
+ to_path
347
+ }).freeze,
348
+ Exception => hashify(%I{
349
+ initialize
350
+ ==
351
+ backtrace
352
+ backtrace_locations
353
+ cause
354
+ exception
355
+ inspect
356
+ message
357
+ to_s
358
+ }).freeze,
359
+ Enumerator => hashify(%I{
360
+ initialize
361
+ each
362
+ each_with_index
363
+ each_with_object
364
+ inspect
365
+ size
366
+ with_index
367
+ with_object
368
+ }).freeze,
369
+ Fiber => hashify(%I{
370
+ alive?
371
+ }).freeze,
372
+ FiberError => hashify(%I{
373
+ initialize
374
+ }).freeze,
375
+ File => hashify(%I{
376
+ path
377
+ to_path
378
+ }).freeze,
379
+ FloatDomainError => hashify(%I{
380
+ initialize
381
+ }).freeze,
382
+ Hash => hashify(%I{
383
+ initialize
384
+ <
385
+ <=
386
+ ==
387
+ >
388
+ >=
389
+ []
390
+ any?
391
+ assoc
392
+ compact
393
+ compare_by_identity?
394
+ default_proc
395
+ dig
396
+ each
397
+ each_key
398
+ each_pair
399
+ each_value
400
+ empty?
401
+ eql?
402
+ fetch
403
+ fetch_values
404
+ flatten
405
+ has_key?
406
+ has_value?
407
+ hash
408
+ include?
409
+ to_s
410
+ inspect
411
+ invert
412
+ key
413
+ key?
414
+ keys
415
+ length
416
+ member?
417
+ merge
418
+ rassoc
419
+ reject
420
+ select
421
+ size
422
+ to_a
423
+ to_h
424
+ to_hash
425
+ to_proc
426
+ transform_values
427
+ value?
428
+ values
429
+ value_at
430
+ }).freeze,
431
+ IndexError => hashify(%I{
432
+ initialize
433
+ }).freeze,
434
+ IO => hashify(%I{
435
+ autoclose?
436
+ binmode?
437
+ close_on_exec?
438
+ closed?
439
+ encoding
440
+ inspect
441
+ internal_encoding
442
+ sync
443
+ }).freeze,
444
+ KeyError => hashify(%I{
445
+ initialize
446
+ }).freeze,
447
+ Method => hashify(%I{
448
+ ==
449
+ []
450
+ arity
451
+ call
452
+ clone
453
+ curry
454
+ eql?
455
+ hash
456
+ inspect
457
+ name
458
+ original_name
459
+ owner
460
+ parameters
461
+ receiver
462
+ source_location
463
+ super_method
464
+ to_proc
465
+ to_s
466
+ }).freeze,
467
+ Module => hashify(%I{
468
+ <
469
+ <=
470
+ <=>
471
+ ==
472
+ ===
473
+ >
474
+ >=
475
+ ancestors
476
+ autoload?
477
+ class_variable_defined?
478
+ class_variable_get
479
+ class_variables
480
+ const_defined?
481
+ const_get
482
+ constants
483
+ include?
484
+ included_modules
485
+ inspect
486
+ instance_method
487
+ instance_methods
488
+ method_defined?
489
+ name
490
+ private_instance_methods
491
+ private_method_defined?
492
+ protected_instance_methods
493
+ protected_method_defined?
494
+ public_instance_method
495
+ public_instance_methods
496
+ public_method_defined?
497
+ singleton_class?
498
+ to_s
499
+ }).freeze,
500
+ Mutex => hashify(%I{
501
+ locked?
502
+ owned?
503
+ }).freeze,
504
+ NameError => hashify(%I{
505
+ initialize
506
+ }).freeze,
507
+ NoMethodError => hashify(%I{
508
+ initialize
509
+ }).freeze,
510
+ RangeError => hashify(%I{
511
+ initialize
512
+ }).freeze,
513
+ RegexpError => hashify(%I{
514
+ initialize
515
+ }).freeze,
516
+ RuntimeError => hashify(%I{
517
+ initialize
518
+ }).freeze,
519
+ String => hashify(%I{
520
+ initialize
521
+ %
522
+ *
523
+ +
524
+ +@
525
+ -@
526
+ <=>
527
+ ==
528
+ ===
529
+ =~
530
+ []
531
+ ascii_only?
532
+ b
533
+ bytes
534
+ bytesize
535
+ byteslice
536
+ capitalize
537
+ casecmp
538
+ casecmp?
539
+ center
540
+ chars
541
+ chomp
542
+ chop
543
+ chr
544
+ codepoints
545
+ count
546
+ crypt
547
+ delete
548
+ downcase
549
+ dump
550
+ each_byte
551
+ each_char
552
+ each_codepoint
553
+ each_line
554
+ empty?
555
+ encoding
556
+ end_with?
557
+ eql?
558
+ getbyte
559
+ gsub
560
+ hash
561
+ hex
562
+ include?
563
+ index
564
+ inspect
565
+ intern
566
+ length
567
+ lines
568
+ ljust
569
+ lstrip
570
+ match
571
+ match?
572
+ next
573
+ oct
574
+ ord
575
+ partition
576
+ reverse
577
+ rindex
578
+ rjust
579
+ rpartition
580
+ rstrip
581
+ scan
582
+ scrub
583
+ size
584
+ slice
585
+ split
586
+ squeeze
587
+ start_with?
588
+ strip
589
+ sub
590
+ succ
591
+ sum
592
+ swapcase
593
+ to_c
594
+ to_f
595
+ to_i
596
+ to_r
597
+ to_s
598
+ to_str
599
+ to_sym
600
+ tr
601
+ tr_s
602
+ unpack
603
+ unpack1
604
+ upcase
605
+ upto
606
+ valid_encoding?
607
+ }).freeze,
608
+ ThreadGroup => hashify(%I{
609
+ enclosed?
610
+ list
611
+ }).freeze,
612
+ Thread => hashify(%I{
613
+ []
614
+ abort_on_exception
615
+ alive?
616
+ backtrace
617
+ backtrace_locations
618
+ group
619
+ inspect
620
+ key?
621
+ keys
622
+ name
623
+ pending_interrupt?
624
+ priority
625
+ report_on_exception
626
+ safe_level
627
+ status
628
+ stop?
629
+ thread_variable?
630
+ thread_variable_get
631
+ thread_variables
632
+ }).freeze,
633
+ Time => hashify(%I{
634
+ initialize
635
+ +
636
+ -
637
+ <=>
638
+ asctime
639
+ ctime
640
+ day
641
+ dst?
642
+ eql?
643
+ friday?
644
+ getgm
645
+ getlocal
646
+ getuc
647
+ gmt
648
+ gmt_offset
649
+ gmtoff
650
+ hash
651
+ hour
652
+ inspect
653
+ isdst
654
+ mday
655
+ min
656
+ mon
657
+ month
658
+ monday?
659
+ month
660
+ nsec
661
+ round
662
+ saturday?
663
+ sec
664
+ strftime
665
+ subsec
666
+ succ
667
+ sunday?
668
+ thursday?
669
+ to_a
670
+ to_f
671
+ to_i
672
+ to_r
673
+ to_s
674
+ tuesday?
675
+ tv_nsec
676
+ tv_sec
677
+ tv_usec
678
+ usec
679
+ utc?
680
+ utc_offset
681
+ wday
682
+ wednesday?
683
+ yday
684
+ year
685
+ zone
686
+ }).freeze,
687
+ TypeError => hashify(%I{
688
+ initialize
689
+ }).freeze,
690
+ UnboundMethod => hashify(%I{
691
+ ==
692
+ arity
693
+ clone
694
+ eql?
695
+ hash
696
+ inspect
697
+ name
698
+ original_name
699
+ owner
700
+ parameters
701
+ source_location
702
+ super_method
703
+ to_s
704
+ }).freeze,
705
+ ZeroDivisionError => hashify(%I{
706
+ initialize
707
+ }).freeze,
708
+ # Modules
709
+ Kernel => hashify(%I{
710
+ Array
711
+ Complex
712
+ Float
713
+ Hash
714
+ Integer
715
+ Rational
716
+ String
717
+ __callee__
718
+ __dir__
719
+ __method__
720
+ autoload?
721
+ block_given?
722
+ caller
723
+ caller_locations
724
+ catch
725
+ format
726
+ global_variables
727
+ iterator?
728
+ lambda
729
+ local_variables
730
+ loop
731
+ method
732
+ methods
733
+ proc
734
+ rand
735
+ !~
736
+ <=>
737
+ ===
738
+ =~
739
+ class
740
+ clone
741
+ dup
742
+ enum_for
743
+ eql?
744
+ frozen?
745
+ hash
746
+ inspect
747
+ instance_of?
748
+ instance_variable_defined?
749
+ instance_variable_get
750
+ instance_variables
751
+ is_a?
752
+ itself
753
+ kind_of?
754
+ nil?
755
+ object_id
756
+ private_methods
757
+ protected_methods
758
+ public_method
759
+ public_methods
760
+ public_send
761
+ respond_to?
762
+ respond_to_missing?
763
+ __send__
764
+ send
765
+ singleton_class
766
+ singleton_method
767
+ singleton_methods
768
+ tainted?
769
+ tap
770
+ to_enum
771
+ to_s
772
+ untrusted?
773
+ }).freeze
774
+ }.freeze
775
+
776
+ class << self
777
+ ##
778
+ # Evaluates call stack. Collects function name and location of each
779
+ # frame from given binding objects. Collects local variable
780
+ # information from top frames.
781
+ #
782
+ # @param [Array<Binding>] call_stack_bindings A list of binding
783
+ # objects that come from each of the call stack frames.
784
+ # @return [Array<Google::Cloud::Debugger::Breakpoint::StackFrame>]
785
+ # A list of StackFrame objects that represent state of the
786
+ # call stack
787
+ #
788
+ def eval_call_stack call_stack_bindings
789
+ result = []
790
+ call_stack_bindings.each_with_index do |frame_binding, i|
791
+ frame_info = StackFrame.new.tap do |sf|
792
+ sf.function = frame_binding.eval("__method__").to_s
793
+ sf.location = SourceLocation.new.tap do |l|
794
+ l.path =
795
+ frame_binding.eval("::File.absolute_path(__FILE__)")
796
+ l.line = frame_binding.eval("__LINE__")
797
+ end
798
+ end
799
+
800
+ if i < STACK_EVAL_DEPTH
801
+ frame_info.locals = eval_frame_variables frame_binding
802
+ end
803
+
804
+ result << frame_info
805
+ end
806
+
807
+ result
808
+ end
809
+
810
+ ##
811
+ # Evaluates a boolean conditional expression in the given context
812
+ # binding. The evaluation subjects to the read-only rules. If
813
+ # the expression does any write operation, the evaluation aborts
814
+ # and returns false.
815
+ #
816
+ # @param [Binding] binding The binding object from the context
817
+ # @param [String] condition A string of code to be evaluates
818
+ #
819
+ # @return [Boolean] True if condition expression read-only evaluates
820
+ # to true. Otherwise false.
821
+ #
822
+ def eval_condition binding, condition
823
+ result = readonly_eval_expression_exec binding, condition
824
+
825
+ if result.is_a?(Exception) &&
826
+ result.instance_variable_get(:@mutation_cause)
827
+ return false
828
+ end
829
+
830
+ result ? true : false
831
+ end
832
+
833
+ ##
834
+ # Evaluates the breakpoint expressions at the point that triggered
835
+ # the breakpoint. The expressions subject to the read-only rules.
836
+ # If the expressions do any write operations, the evaluations abort
837
+ # and show an error message in place of the real result.
838
+ #
839
+ # @param [Binding] binding The binding object from the context
840
+ # @param [Array<String>] expressions A list of code strings to be
841
+ # evaluated
842
+ # @return [Array<Google::Cloud::Debugger::Breakpoint::Variable>]
843
+ # A list of Breakpoint::Variables objects that represent the
844
+ # expression evaluations results.
845
+ #
846
+ def eval_expressions binding, expressions
847
+ expressions.map do |expression|
848
+ eval_result = readonly_eval_expression binding, expression
849
+ evaluated_var = Variable.from_rb_var eval_result
850
+ evaluated_var.name = expression
851
+ evaluated_var
852
+ end
853
+ end
854
+
855
+ ##
856
+ # @private Read-only evaluates a single expression in a given
857
+ # context binding. Handles any exceptions raised.
858
+ #
859
+ # @param [Binding] binding The binding object from the context
860
+ # @param [String] expression A string of code to be evaluates
861
+ #
862
+ # @return [Object] The result Ruby object from evaluating the
863
+ # expression. If the expression is blocked from mutating
864
+ # the state of program. An error message is returned instead.
865
+ #
866
+ def readonly_eval_expression binding, expression
867
+ begin
868
+ result = readonly_eval_expression_exec binding, expression
869
+ rescue => e
870
+ result = "Unable to evaluate expression: #{e.message}"
871
+ end
872
+
873
+ if result.is_a?(Exception) &&
874
+ result.instance_variable_get(:@mutation_cause)
875
+ return "Error: #{result.message}"
876
+ end
877
+
878
+ result
879
+ end
880
+
881
+ private
882
+
883
+ ##
884
+ # @private Actually read-only evaluates an expression in a given
885
+ # context binding. The evaluation is done in a separate thread due
886
+ # to this method may be run from Ruby Trace call back, where
887
+ # addtional code tracing is disabled in original thread.
888
+ #
889
+ # @param [Binding] binding The binding object from the context
890
+ # @param [String] expression A string of code to be evaluates
891
+ #
892
+ # @return [Object] The result Ruby object from evaluating the
893
+ # expression. It returns Google::Cloud::Debugger::MutationError
894
+ # if a mutation is caught.
895
+ #
896
+ def readonly_eval_expression_exec binding, expression
897
+ compilation_result = validate_compiled_expression expression
898
+ return compilation_result if compilation_result.is_a?(Exception)
899
+
900
+ # The evaluation is most likely triggered from a trace callback,
901
+ # where addtional nested tracing is disabled by VM. So we need to
902
+ # do evaluation in a new thread, where function calls can be
903
+ # traced.
904
+ thr = Thread.new do
905
+ begin
906
+ binding.eval wrap_expression(expression)
907
+ rescue => e
908
+ # Threat all StandardError as mutation and set @mutation_cause
909
+ unless e.instance_variable_get :@mutation_cause
910
+ e.instance_variable_set(
911
+ :@mutation_cause,
912
+ Google::Cloud::Debugger::MutationError::UNKNOWN_CAUSE)
913
+ end
914
+ e
915
+ end
916
+ end
917
+
918
+ thr.join.value
919
+ end
920
+
921
+ ##
922
+ # @private Compile the expression into YARV instructions. Return
923
+ # Google::Cloud::Debugger::MutationError if any prohibited YARV
924
+ # instructions are found.
925
+ #
926
+ # @param [String] expression String of code expression
927
+ #
928
+ # @return [String,Google::Cloud::Debugger::MutationError] It returns
929
+ # the compile YARV instructions if no prohibited bytecodes are
930
+ # found. Otherwise return Google::Cloud::Debugger::MutationError.
931
+ #
932
+ def validate_compiled_expression expression
933
+ begin
934
+ yarv_instructions =
935
+ RubyVM::InstructionSequence.compile(expression).disasm
936
+ rescue ScriptError
937
+ return Google::Cloud::Debugger::MutationError.new(
938
+ "Unable to compile expression",
939
+ Google::Cloud::Debugger::MutationError::PROHIBITED_YARV
940
+ )
941
+ end
942
+
943
+ unless immutable_yarv_instructions? yarv_instructions
944
+ return Google::Cloud::Debugger::MutationError.new(
945
+ "Mutation detected!",
946
+ Google::Cloud::Debugger::MutationError::PROHIBITED_YARV
947
+ )
948
+ end
949
+
950
+ yarv_instructions
951
+ end
952
+
953
+ ##
954
+ # @private Helps evaluating local variables from a single frame
955
+ # binding
956
+ #
957
+ # @param [Binding] frame_binding The context binding object from
958
+ # a given frame.
959
+ # @return [Array<Google::Cloud::Debugger::Variable>] A list of
960
+ # Breakpoint::Variables that represent all the local variables
961
+ # in a context frame.
962
+ #
963
+ def eval_frame_variables frame_binding
964
+ result_variables = []
965
+ result_variables +=
966
+ frame_binding.local_variables.map do |local_var_name|
967
+ local_var = frame_binding.local_variable_get(local_var_name)
968
+
969
+ Variable.from_rb_var(local_var, name: local_var_name)
970
+ end
971
+
972
+ result_variables
973
+ end
974
+
975
+ ##
976
+ # @private Helps checking if a given set of YARV instructions
977
+ # contains any prohibited bytecode or instructions.
978
+ #
979
+ # @param [String] yarv_instructions Compiled YARV instructions
980
+ # string
981
+ # @param [Boolean] allow_localops Whether allows local variable
982
+ # write operations
983
+ #
984
+ # @return [Boolean] True if the YARV instructions don't contain any
985
+ # prohibited operations. Otherwise false.
986
+ #
987
+ def immutable_yarv_instructions? yarv_instructions,
988
+ allow_localops: false
989
+ if allow_localops
990
+ byte_code_blacklist_regex = BYTE_CODE_BLACKLIST_REGEX
991
+ else
992
+ byte_code_blacklist_regex = FULL_BYTE_CODE_BLACKLIST_REGEX
993
+ end
994
+
995
+ func_call_flag_blacklist_regex = FUNC_CALL_FLAG_BLACKLIST_REGEX
996
+
997
+ catch_table_type_blacklist_regex = CATCH_TABLE_BLACKLIST_REGEX
998
+
999
+ !(yarv_instructions.match(func_call_flag_blacklist_regex) ||
1000
+ yarv_instructions.match(byte_code_blacklist_regex) ||
1001
+ yarv_instructions.match(catch_table_type_blacklist_regex))
1002
+ end
1003
+
1004
+ ##
1005
+ # @private Wraps expression with tracing code
1006
+ def wrap_expression expression
1007
+ """
1008
+ begin
1009
+ Google::Cloud::Debugger::Breakpoint::Evaluator.send(
1010
+ :enable_method_trace_for_thread)
1011
+ #{expression}
1012
+ ensure
1013
+ Google::Cloud::Debugger::Breakpoint::Evaluator.send(
1014
+ :disable_method_trace_for_thread)
1015
+ end
1016
+ """
1017
+ end
1018
+
1019
+ ##
1020
+ # @private Evaluation tracing callback function. This is called
1021
+ # everytime a Ruby function is called during evaluation of
1022
+ # an expression.
1023
+ #
1024
+ # @param [Object] receiver The receiver of the function being called
1025
+ # @param [Symbol] mid The method name
1026
+ #
1027
+ # @return [NilClass] Nil if no prohibited operations are found.
1028
+ # Otherwise raise Google::Cloud::Debugger::MutationError error.
1029
+ #
1030
+ def trace_func_callback receiver, mid
1031
+ meth = receiver.method mid
1032
+ yarv_instructions = RubyVM::InstructionSequence.disasm meth
1033
+
1034
+ return if immutable_yarv_instructions?(yarv_instructions,
1035
+ allow_localops: true)
1036
+ fail Google::Cloud::Debugger::MutationError.new(
1037
+ "Mutation detected!",
1038
+ Google::Cloud::Debugger::MutationError::PROHIBITED_YARV)
1039
+ end
1040
+
1041
+ ##
1042
+ # @private Evaluation tracing callback function. This is called
1043
+ # everytime a C function is called during evaluation of
1044
+ # an expression.
1045
+ #
1046
+ # @param [Object] receiver The receiver of the function being called
1047
+ # @param [Class] defined_class The Class of where the function is
1048
+ # defined
1049
+ # @param [Symbol] mid The method name
1050
+ #
1051
+ # @return [NilClass] Nil if no prohibited operations are found.
1052
+ # Otherwise raise Google::Cloud::Debugger::MutationError error.
1053
+ #
1054
+ def trace_c_func_callback receiver, defined_class, mid
1055
+ if receiver.is_a?(Class) || receiver.is_a?(Module)
1056
+ invalid_op =
1057
+ !validate_c_class_method(defined_class, receiver, mid)
1058
+ else
1059
+ invalid_op = !validate_c_instance_method(defined_class, mid)
1060
+ end
1061
+
1062
+ return unless invalid_op
1063
+
1064
+ Google::Cloud::Debugger::Breakpoint::Evaluator.send(
1065
+ :disable_method_trace_for_thread)
1066
+ fail Google::Cloud::Debugger::MutationError.new(
1067
+ "Invalid operation detected",
1068
+ Google::Cloud::Debugger::MutationError::PROHIBITED_C_FUNC)
1069
+ end
1070
+
1071
+ ##
1072
+ # @private Helper method to verify wehter a C level class method
1073
+ # is allowed or not.
1074
+ def validate_c_class_method klass, receiver, mid
1075
+ IMMUTABLE_CLASSES.include?(receiver) ||
1076
+ (C_CLASS_METHOD_WHITELIST[receiver] || {})[mid] ||
1077
+ (C_INSTANCE_METHOD_WHITELIST[klass] || {})[mid]
1078
+ end
1079
+
1080
+ ##
1081
+ # @private Helper method to verify wehter a C level instance method
1082
+ # is allowed or not.
1083
+ def validate_c_instance_method klass, mid
1084
+ IMMUTABLE_CLASSES.include?(klass) ||
1085
+ (C_INSTANCE_METHOD_WHITELIST[klass] || {})[mid]
1086
+ end
1087
+ end
1088
+ end
1089
+ end
1090
+
1091
+ ##
1092
+ # @private Custom error type used to identify mutation during breakpoint
1093
+ # expression evaluations
1094
+ class MutationError < StandardError
1095
+ UNKNOWN_CAUSE = Object.new.freeze
1096
+ PROHIBITED_YARV = Object.new.freeze
1097
+ PROHIBITED_C_FUNC = Object.new.freeze
1098
+
1099
+ attr_reader :mutation_cause
1100
+
1101
+ def initialize msg = "Mutation detected!",
1102
+ mutation_cause = UNKNOWN_CAUSE
1103
+ @mutation_cause = mutation_cause
1104
+ super(msg)
1105
+ end
1106
+
1107
+ def inspect
1108
+ "#<MutationError: #{message}>"
1109
+ end
1110
+ end
1111
+ end
1112
+ end
1113
+ end