google-cloud-debugger 0.24.0

Sign up to get free protection for your applications and to get access to all the features.
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