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.
- checksums.yaml +7 -0
- data/.yardopts +8 -0
- data/LICENSE +201 -0
- data/README.md +56 -0
- data/ext/google/cloud/debugger/debugger_c/debugger.c +31 -0
- data/ext/google/cloud/debugger/debugger_c/debugger.h +26 -0
- data/ext/google/cloud/debugger/debugger_c/evaluator.c +78 -0
- data/ext/google/cloud/debugger/debugger_c/evaluator.h +25 -0
- data/ext/google/cloud/debugger/debugger_c/extconf.rb +22 -0
- data/ext/google/cloud/debugger/debugger_c/tracer.c +478 -0
- data/ext/google/cloud/debugger/debugger_c/tracer.h +31 -0
- data/lib/google-cloud-debugger.rb +121 -0
- data/lib/google/cloud/debugger.rb +379 -0
- data/lib/google/cloud/debugger/agent.rb +204 -0
- data/lib/google/cloud/debugger/async_actor.rb +290 -0
- data/lib/google/cloud/debugger/breakpoint.rb +382 -0
- data/lib/google/cloud/debugger/breakpoint/evaluator.rb +1113 -0
- data/lib/google/cloud/debugger/breakpoint/source_location.rb +75 -0
- data/lib/google/cloud/debugger/breakpoint/stack_frame.rb +109 -0
- data/lib/google/cloud/debugger/breakpoint/variable.rb +304 -0
- data/lib/google/cloud/debugger/breakpoint_manager.rb +217 -0
- data/lib/google/cloud/debugger/credentials.rb +41 -0
- data/lib/google/cloud/debugger/debuggee.rb +204 -0
- data/lib/google/cloud/debugger/debuggee/app_uniquifier_generator.rb +78 -0
- data/lib/google/cloud/debugger/middleware.rb +77 -0
- data/lib/google/cloud/debugger/project.rb +135 -0
- data/lib/google/cloud/debugger/rails.rb +141 -0
- data/lib/google/cloud/debugger/service.rb +130 -0
- data/lib/google/cloud/debugger/tracer.rb +165 -0
- data/lib/google/cloud/debugger/transmitter.rb +129 -0
- data/lib/google/cloud/debugger/v2.rb +15 -0
- data/lib/google/cloud/debugger/v2/controller2_client.rb +299 -0
- data/lib/google/cloud/debugger/v2/controller2_client_config.json +43 -0
- data/lib/google/cloud/debugger/v2/debugger2_client.rb +378 -0
- data/lib/google/cloud/debugger/v2/debugger2_client_config.json +53 -0
- data/lib/google/cloud/debugger/v2/doc/google/devtools/clouddebugger/v2/data.rb +441 -0
- data/lib/google/cloud/debugger/v2/doc/google/devtools/clouddebugger/v2/debugger.rb +151 -0
- data/lib/google/cloud/debugger/v2/doc/google/devtools/source/v1/source_context.rb +161 -0
- data/lib/google/cloud/debugger/v2/doc/google/protobuf/timestamp.rb +81 -0
- data/lib/google/cloud/debugger/version.rb +22 -0
- data/lib/google/devtools/clouddebugger/v2/controller_pb.rb +47 -0
- data/lib/google/devtools/clouddebugger/v2/controller_services_pb.rb +97 -0
- data/lib/google/devtools/clouddebugger/v2/data_pb.rb +105 -0
- data/lib/google/devtools/clouddebugger/v2/debugger_pb.rb +74 -0
- data/lib/google/devtools/clouddebugger/v2/debugger_services_pb.rb +64 -0
- data/lib/google/devtools/source/v1/source_context_pb.rb +89 -0
- 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
|