google-cloud-debugger 0.40.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 +18 -0
- data/AUTHENTICATION.md +178 -0
- data/CHANGELOG.md +233 -0
- data/CODE_OF_CONDUCT.md +40 -0
- data/CONTRIBUTING.md +188 -0
- data/INSTRUMENTATION.md +115 -0
- data/LICENSE +201 -0
- data/LOGGING.md +32 -0
- data/OVERVIEW.md +266 -0
- data/TROUBLESHOOTING.md +31 -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 +115 -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 +542 -0
- data/ext/google/cloud/debugger/debugger_c/tracer.h +25 -0
- data/lib/google-cloud-debugger.rb +181 -0
- data/lib/google/cloud/debugger.rb +259 -0
- data/lib/google/cloud/debugger/agent.rb +255 -0
- data/lib/google/cloud/debugger/backoff.rb +70 -0
- data/lib/google/cloud/debugger/breakpoint.rb +443 -0
- data/lib/google/cloud/debugger/breakpoint/evaluator.rb +1099 -0
- data/lib/google/cloud/debugger/breakpoint/source_location.rb +74 -0
- data/lib/google/cloud/debugger/breakpoint/stack_frame.rb +109 -0
- data/lib/google/cloud/debugger/breakpoint/status_message.rb +93 -0
- data/lib/google/cloud/debugger/breakpoint/validator.rb +92 -0
- data/lib/google/cloud/debugger/breakpoint/variable.rb +595 -0
- data/lib/google/cloud/debugger/breakpoint/variable_table.rb +96 -0
- data/lib/google/cloud/debugger/breakpoint_manager.rb +311 -0
- data/lib/google/cloud/debugger/credentials.rb +50 -0
- data/lib/google/cloud/debugger/debuggee.rb +222 -0
- data/lib/google/cloud/debugger/debuggee/app_uniquifier_generator.rb +76 -0
- data/lib/google/cloud/debugger/logpoint.rb +98 -0
- data/lib/google/cloud/debugger/middleware.rb +200 -0
- data/lib/google/cloud/debugger/project.rb +110 -0
- data/lib/google/cloud/debugger/rails.rb +174 -0
- data/lib/google/cloud/debugger/request_quota_manager.rb +95 -0
- data/lib/google/cloud/debugger/service.rb +88 -0
- data/lib/google/cloud/debugger/snappoint.rb +208 -0
- data/lib/google/cloud/debugger/tracer.rb +137 -0
- data/lib/google/cloud/debugger/transmitter.rb +199 -0
- data/lib/google/cloud/debugger/version.rb +22 -0
- metadata +353 -0
@@ -0,0 +1,1099 @@
|
|
1
|
+
# Copyright 2017 Google LLC
|
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
|
+
# https://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
|
+
class Evaluator
|
41
|
+
##
|
42
|
+
# @private YARV bytecode that the evaluator blocks during expression
|
43
|
+
# evaluation. If the breakpoint contains expressions that uses the
|
44
|
+
# following bytecode, the evaluator will block the expression
|
45
|
+
# evaluation from execusion.
|
46
|
+
BYTE_CODE_BLACKLIST = %w[
|
47
|
+
setinstancevariable
|
48
|
+
setclassvariable
|
49
|
+
setconstant
|
50
|
+
setglobal
|
51
|
+
defineclass
|
52
|
+
opt_ltlt
|
53
|
+
opt_aset
|
54
|
+
opt_aset_with
|
55
|
+
].freeze
|
56
|
+
|
57
|
+
##
|
58
|
+
# @private YARV bytecode that the evaluator blocks during expression
|
59
|
+
# evaluation on the top level. (not from within by predefined methods)
|
60
|
+
LOCAL_BYTE_CODE_BLACKLIST = %w[
|
61
|
+
setlocal
|
62
|
+
].freeze
|
63
|
+
|
64
|
+
##
|
65
|
+
# @private YARV bytecode call flags that the evaluator blocks during
|
66
|
+
# expression evaluation
|
67
|
+
FUNC_CALL_FLAG_BLACKLIST = %w[
|
68
|
+
ARGS_BLOCKARG
|
69
|
+
].freeze
|
70
|
+
|
71
|
+
##
|
72
|
+
# @private YARV instructions catch table type that the evaluator
|
73
|
+
# blocks during expression evaluation
|
74
|
+
CATCH_TABLE_TYPE_BLACKLIST = %w[
|
75
|
+
rescue StandardError
|
76
|
+
].freeze
|
77
|
+
|
78
|
+
##
|
79
|
+
# @private Predefined regex. Saves time during runtime.
|
80
|
+
BYTE_CODE_BLACKLIST_REGEX =
|
81
|
+
/^\d+ #{BYTE_CODE_BLACKLIST.join '|'}/.freeze
|
82
|
+
|
83
|
+
##
|
84
|
+
# @private Predefined regex. Saves time during runtime.
|
85
|
+
FULL_BYTE_CODE_BLACKLIST_REGEX = /^\d+ #{
|
86
|
+
[*BYTE_CODE_BLACKLIST, *LOCAL_BYTE_CODE_BLACKLIST].join '|'
|
87
|
+
}/.freeze
|
88
|
+
|
89
|
+
##
|
90
|
+
# @private Predefined regex. Saves time during runtime.
|
91
|
+
FUNC_CALL_FLAG_BLACKLIST_REGEX =
|
92
|
+
/<call(info|data)!.+#{FUNC_CALL_FLAG_BLACKLIST.join '|'}/.freeze
|
93
|
+
|
94
|
+
##
|
95
|
+
# @private Predefined regex. Saves time during runtime.
|
96
|
+
CATCH_TABLE_BLACKLIST_REGEX =
|
97
|
+
/catch table.*catch type: #{
|
98
|
+
CATCH_TABLE_TYPE_BLACKLIST.join '|'
|
99
|
+
}/m.freeze
|
100
|
+
|
101
|
+
private_constant :BYTE_CODE_BLACKLIST_REGEX,
|
102
|
+
:FULL_BYTE_CODE_BLACKLIST_REGEX,
|
103
|
+
:FUNC_CALL_FLAG_BLACKLIST_REGEX,
|
104
|
+
:CATCH_TABLE_BLACKLIST_REGEX
|
105
|
+
|
106
|
+
##
|
107
|
+
# @private List of pre-approved classes to be used during expression
|
108
|
+
# evaluation.
|
109
|
+
IMMUTABLE_CLASSES = [
|
110
|
+
Complex,
|
111
|
+
FalseClass,
|
112
|
+
Float,
|
113
|
+
Integer,
|
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
|
+
].freeze
|
127
|
+
|
128
|
+
##
|
129
|
+
# @private helper method to hashify an array
|
130
|
+
def self.hashify ary
|
131
|
+
ary.each.with_index(1).to_h
|
132
|
+
end
|
133
|
+
private_class_method :hashify
|
134
|
+
|
135
|
+
##
|
136
|
+
# @private List of C level class methods that the evaluator allows
|
137
|
+
# during expression evaluation
|
138
|
+
C_CLASS_METHOD_WHITELIST = {
|
139
|
+
# Classes
|
140
|
+
ArgumentError => hashify(%I[new]).freeze,
|
141
|
+
Array => hashify(%I[new [] try_convert]).freeze,
|
142
|
+
BasicObject => hashify(%I[new]).freeze,
|
143
|
+
Exception => hashify(%I[exception new]).freeze,
|
144
|
+
Enumerator => hashify(%I[new]).freeze,
|
145
|
+
Fiber => hashify(%I[current]).freeze,
|
146
|
+
FiberError => hashify(%I[new]).freeze,
|
147
|
+
File => hashify(
|
148
|
+
%I[
|
149
|
+
basename
|
150
|
+
dirname
|
151
|
+
extname
|
152
|
+
join
|
153
|
+
path
|
154
|
+
split
|
155
|
+
]
|
156
|
+
).freeze,
|
157
|
+
FloatDomainError => hashify(%I[new]).freeze,
|
158
|
+
Hash => hashify(%I[new [] try_convert]).freeze,
|
159
|
+
IndexError => hashify(%I[new]).freeze,
|
160
|
+
KeyError => hashify(%I[new]).freeze,
|
161
|
+
Module => hashify(%I[constants nesting used_modules]).freeze,
|
162
|
+
NameError => hashify(%I[new]).freeze,
|
163
|
+
NoMethodError => hashify(%I[new]).freeze,
|
164
|
+
Object => hashify(%I[new]).freeze,
|
165
|
+
RangeError => hashify(%I[new]).freeze,
|
166
|
+
RegexpError => hashify(%I[new]).freeze,
|
167
|
+
RuntimeError => hashify(%I[new]).freeze,
|
168
|
+
String => hashify(%I[new try_convert]).freeze,
|
169
|
+
Thread => hashify(
|
170
|
+
%I[
|
171
|
+
DEBUG
|
172
|
+
abort_on_exception
|
173
|
+
current
|
174
|
+
list
|
175
|
+
main
|
176
|
+
pending_interrupt?
|
177
|
+
report_on_exception
|
178
|
+
]
|
179
|
+
).freeze,
|
180
|
+
Time => hashify(
|
181
|
+
%I[
|
182
|
+
at
|
183
|
+
gm
|
184
|
+
local
|
185
|
+
mktime
|
186
|
+
new
|
187
|
+
now
|
188
|
+
utc
|
189
|
+
]
|
190
|
+
).freeze,
|
191
|
+
TypeError => hashify(%I[new]).freeze,
|
192
|
+
Google::Cloud::Debugger::Breakpoint::Evaluator => hashify(
|
193
|
+
%I[disable_method_trace_for_thread]
|
194
|
+
).freeze,
|
195
|
+
ZeroDivisionError => hashify(%I[new]).freeze
|
196
|
+
}.freeze
|
197
|
+
|
198
|
+
##
|
199
|
+
# @private List of C level instance methods that the evaluator allows
|
200
|
+
# during expression evaluation
|
201
|
+
C_INSTANCE_METHOD_WHITELIST = {
|
202
|
+
ArgumentError => hashify(%I[initialize]).freeze,
|
203
|
+
Array => hashify(
|
204
|
+
%I[
|
205
|
+
initialize
|
206
|
+
&
|
207
|
+
*
|
208
|
+
+
|
209
|
+
-
|
210
|
+
<=>
|
211
|
+
==
|
212
|
+
any?
|
213
|
+
assoc
|
214
|
+
at
|
215
|
+
bsearch
|
216
|
+
bsearch_index
|
217
|
+
collect
|
218
|
+
combination
|
219
|
+
compact
|
220
|
+
[]
|
221
|
+
count
|
222
|
+
cycle
|
223
|
+
dig
|
224
|
+
drop
|
225
|
+
drop_while
|
226
|
+
each
|
227
|
+
each_index
|
228
|
+
empty?
|
229
|
+
eql?
|
230
|
+
fetch
|
231
|
+
find_index
|
232
|
+
first
|
233
|
+
flatten
|
234
|
+
frozen?
|
235
|
+
hash
|
236
|
+
include?
|
237
|
+
index
|
238
|
+
inspect
|
239
|
+
to_s
|
240
|
+
join
|
241
|
+
last
|
242
|
+
length
|
243
|
+
map
|
244
|
+
max
|
245
|
+
min
|
246
|
+
pack
|
247
|
+
permutation
|
248
|
+
product
|
249
|
+
rassoc
|
250
|
+
reject
|
251
|
+
repeated_combination
|
252
|
+
repeated_permutation
|
253
|
+
reverse
|
254
|
+
reverse_each
|
255
|
+
rindex
|
256
|
+
rotate
|
257
|
+
sample
|
258
|
+
select
|
259
|
+
shuffle
|
260
|
+
size
|
261
|
+
slice
|
262
|
+
sort
|
263
|
+
sum
|
264
|
+
take
|
265
|
+
take_while
|
266
|
+
to_a
|
267
|
+
to_ary
|
268
|
+
to_h
|
269
|
+
transpose
|
270
|
+
uniq
|
271
|
+
values_at
|
272
|
+
zip
|
273
|
+
|
|
274
|
+
]
|
275
|
+
).freeze,
|
276
|
+
BasicObject => hashify(
|
277
|
+
%I[
|
278
|
+
initialize
|
279
|
+
!
|
280
|
+
!=
|
281
|
+
==
|
282
|
+
__id__
|
283
|
+
method_missing
|
284
|
+
object_id
|
285
|
+
send
|
286
|
+
__send__
|
287
|
+
equal?
|
288
|
+
]
|
289
|
+
).freeze,
|
290
|
+
Binding => hashify(
|
291
|
+
%I[
|
292
|
+
local_variable_defined?
|
293
|
+
local_variable_get
|
294
|
+
local_variables
|
295
|
+
receiver
|
296
|
+
]
|
297
|
+
).freeze,
|
298
|
+
Class => hashify(%I[superclass]).freeze,
|
299
|
+
Dir => hashify(%I[inspect path to_path]).freeze,
|
300
|
+
Exception => hashify(
|
301
|
+
%I[
|
302
|
+
initialize
|
303
|
+
==
|
304
|
+
backtrace
|
305
|
+
backtrace_locations
|
306
|
+
cause
|
307
|
+
exception
|
308
|
+
inspect
|
309
|
+
message
|
310
|
+
to_s
|
311
|
+
]
|
312
|
+
).freeze,
|
313
|
+
Enumerator => hashify(
|
314
|
+
%I[
|
315
|
+
initialize
|
316
|
+
each
|
317
|
+
each_with_index
|
318
|
+
each_with_object
|
319
|
+
inspect
|
320
|
+
size
|
321
|
+
with_index
|
322
|
+
with_object
|
323
|
+
]
|
324
|
+
).freeze,
|
325
|
+
Fiber => hashify(%I[alive?]).freeze,
|
326
|
+
FiberError => hashify(%I[initialize]).freeze,
|
327
|
+
File => hashify(%I[path to_path]).freeze,
|
328
|
+
FloatDomainError => hashify(%I[initialize]).freeze,
|
329
|
+
Hash => hashify(
|
330
|
+
%I[
|
331
|
+
initialize
|
332
|
+
<
|
333
|
+
<=
|
334
|
+
==
|
335
|
+
>
|
336
|
+
>=
|
337
|
+
[]
|
338
|
+
any?
|
339
|
+
assoc
|
340
|
+
compact
|
341
|
+
compare_by_identity?
|
342
|
+
default_proc
|
343
|
+
dig
|
344
|
+
each
|
345
|
+
each_key
|
346
|
+
each_pair
|
347
|
+
each_value
|
348
|
+
empty?
|
349
|
+
eql?
|
350
|
+
fetch
|
351
|
+
fetch_values
|
352
|
+
flatten
|
353
|
+
has_key?
|
354
|
+
has_value?
|
355
|
+
hash
|
356
|
+
include?
|
357
|
+
to_s
|
358
|
+
inspect
|
359
|
+
invert
|
360
|
+
key
|
361
|
+
key?
|
362
|
+
keys
|
363
|
+
length
|
364
|
+
member?
|
365
|
+
merge
|
366
|
+
rassoc
|
367
|
+
reject
|
368
|
+
select
|
369
|
+
size
|
370
|
+
to_a
|
371
|
+
to_h
|
372
|
+
to_hash
|
373
|
+
to_proc
|
374
|
+
transform_values
|
375
|
+
value?
|
376
|
+
values
|
377
|
+
value_at
|
378
|
+
]
|
379
|
+
).freeze,
|
380
|
+
IndexError => hashify(%I[initialize]).freeze,
|
381
|
+
IO => hashify(
|
382
|
+
%I[
|
383
|
+
autoclose?
|
384
|
+
binmode?
|
385
|
+
close_on_exec?
|
386
|
+
closed?
|
387
|
+
encoding
|
388
|
+
inspect
|
389
|
+
internal_encoding
|
390
|
+
sync
|
391
|
+
]
|
392
|
+
).freeze,
|
393
|
+
KeyError => hashify(%I[initialize]).freeze,
|
394
|
+
Method => hashify(
|
395
|
+
%I[
|
396
|
+
==
|
397
|
+
[]
|
398
|
+
arity
|
399
|
+
call
|
400
|
+
clone
|
401
|
+
curry
|
402
|
+
eql?
|
403
|
+
hash
|
404
|
+
inspect
|
405
|
+
name
|
406
|
+
original_name
|
407
|
+
owner
|
408
|
+
parameters
|
409
|
+
receiver
|
410
|
+
source_location
|
411
|
+
super_method
|
412
|
+
to_proc
|
413
|
+
to_s
|
414
|
+
]
|
415
|
+
).freeze,
|
416
|
+
Module => hashify(
|
417
|
+
%I[
|
418
|
+
<
|
419
|
+
<=
|
420
|
+
<=>
|
421
|
+
==
|
422
|
+
===
|
423
|
+
>
|
424
|
+
>=
|
425
|
+
ancestors
|
426
|
+
autoload?
|
427
|
+
class_variable_defined?
|
428
|
+
class_variable_get
|
429
|
+
class_variables
|
430
|
+
const_defined?
|
431
|
+
const_get
|
432
|
+
constants
|
433
|
+
include?
|
434
|
+
included_modules
|
435
|
+
inspect
|
436
|
+
instance_method
|
437
|
+
instance_methods
|
438
|
+
method_defined?
|
439
|
+
name
|
440
|
+
private_instance_methods
|
441
|
+
private_method_defined?
|
442
|
+
protected_instance_methods
|
443
|
+
protected_method_defined?
|
444
|
+
public_instance_method
|
445
|
+
public_instance_methods
|
446
|
+
public_method_defined?
|
447
|
+
singleton_class?
|
448
|
+
to_s
|
449
|
+
]
|
450
|
+
).freeze,
|
451
|
+
Mutex => hashify(%I[locked? owned?]).freeze,
|
452
|
+
NameError => hashify(%I[initialize]).freeze,
|
453
|
+
NoMethodError => hashify(%I[initialize]).freeze,
|
454
|
+
RangeError => hashify(%I[initialize]).freeze,
|
455
|
+
RegexpError => hashify(%I[initialize]).freeze,
|
456
|
+
RuntimeError => hashify(%I[initialize]).freeze,
|
457
|
+
String => hashify(
|
458
|
+
%I[
|
459
|
+
initialize
|
460
|
+
%
|
461
|
+
*
|
462
|
+
+
|
463
|
+
+@
|
464
|
+
-@
|
465
|
+
<=>
|
466
|
+
==
|
467
|
+
===
|
468
|
+
=~
|
469
|
+
[]
|
470
|
+
ascii_only?
|
471
|
+
b
|
472
|
+
bytes
|
473
|
+
bytesize
|
474
|
+
byteslice
|
475
|
+
capitalize
|
476
|
+
casecmp
|
477
|
+
casecmp?
|
478
|
+
center
|
479
|
+
chars
|
480
|
+
chomp
|
481
|
+
chop
|
482
|
+
chr
|
483
|
+
codepoints
|
484
|
+
count
|
485
|
+
crypt
|
486
|
+
delete
|
487
|
+
downcase
|
488
|
+
dump
|
489
|
+
each_byte
|
490
|
+
each_char
|
491
|
+
each_codepoint
|
492
|
+
each_line
|
493
|
+
empty?
|
494
|
+
encoding
|
495
|
+
end_with?
|
496
|
+
eql?
|
497
|
+
getbyte
|
498
|
+
gsub
|
499
|
+
hash
|
500
|
+
hex
|
501
|
+
include?
|
502
|
+
index
|
503
|
+
inspect
|
504
|
+
intern
|
505
|
+
length
|
506
|
+
lines
|
507
|
+
ljust
|
508
|
+
lstrip
|
509
|
+
match
|
510
|
+
match?
|
511
|
+
next
|
512
|
+
oct
|
513
|
+
ord
|
514
|
+
partition
|
515
|
+
reverse
|
516
|
+
rindex
|
517
|
+
rjust
|
518
|
+
rpartition
|
519
|
+
rstrip
|
520
|
+
scan
|
521
|
+
scrub
|
522
|
+
size
|
523
|
+
slice
|
524
|
+
split
|
525
|
+
squeeze
|
526
|
+
start_with?
|
527
|
+
strip
|
528
|
+
sub
|
529
|
+
succ
|
530
|
+
sum
|
531
|
+
swapcase
|
532
|
+
to_c
|
533
|
+
to_f
|
534
|
+
to_i
|
535
|
+
to_r
|
536
|
+
to_s
|
537
|
+
to_str
|
538
|
+
to_sym
|
539
|
+
tr
|
540
|
+
tr_s
|
541
|
+
unpack
|
542
|
+
unpack1
|
543
|
+
upcase
|
544
|
+
upto
|
545
|
+
valid_encoding?
|
546
|
+
]
|
547
|
+
).freeze,
|
548
|
+
ThreadGroup => hashify(%I[enclosed? list]).freeze,
|
549
|
+
Thread => hashify(
|
550
|
+
%I[
|
551
|
+
[]
|
552
|
+
abort_on_exception
|
553
|
+
alive?
|
554
|
+
backtrace
|
555
|
+
backtrace_locations
|
556
|
+
group
|
557
|
+
inspect
|
558
|
+
key?
|
559
|
+
keys
|
560
|
+
name
|
561
|
+
pending_interrupt?
|
562
|
+
priority
|
563
|
+
report_on_exception
|
564
|
+
safe_level
|
565
|
+
status
|
566
|
+
stop?
|
567
|
+
thread_variable?
|
568
|
+
thread_variable_get
|
569
|
+
thread_variables
|
570
|
+
]
|
571
|
+
).freeze,
|
572
|
+
Time => hashify(
|
573
|
+
%I[
|
574
|
+
initialize
|
575
|
+
+
|
576
|
+
-
|
577
|
+
<=>
|
578
|
+
asctime
|
579
|
+
ctime
|
580
|
+
day
|
581
|
+
dst?
|
582
|
+
eql?
|
583
|
+
friday?
|
584
|
+
getgm
|
585
|
+
getlocal
|
586
|
+
getuc
|
587
|
+
gmt
|
588
|
+
gmt_offset
|
589
|
+
gmtoff
|
590
|
+
hash
|
591
|
+
hour
|
592
|
+
inspect
|
593
|
+
isdst
|
594
|
+
mday
|
595
|
+
min
|
596
|
+
mon
|
597
|
+
month
|
598
|
+
monday?
|
599
|
+
month
|
600
|
+
nsec
|
601
|
+
round
|
602
|
+
saturday?
|
603
|
+
sec
|
604
|
+
strftime
|
605
|
+
subsec
|
606
|
+
succ
|
607
|
+
sunday?
|
608
|
+
thursday?
|
609
|
+
to_a
|
610
|
+
to_f
|
611
|
+
to_i
|
612
|
+
to_r
|
613
|
+
to_s
|
614
|
+
tuesday?
|
615
|
+
tv_nsec
|
616
|
+
tv_sec
|
617
|
+
tv_usec
|
618
|
+
usec
|
619
|
+
utc?
|
620
|
+
utc_offset
|
621
|
+
wday
|
622
|
+
wednesday?
|
623
|
+
yday
|
624
|
+
year
|
625
|
+
zone
|
626
|
+
]
|
627
|
+
).freeze,
|
628
|
+
TypeError => hashify(%I[initialize]).freeze,
|
629
|
+
UnboundMethod => hashify(
|
630
|
+
%I[
|
631
|
+
==
|
632
|
+
arity
|
633
|
+
clone
|
634
|
+
eql?
|
635
|
+
hash
|
636
|
+
inspect
|
637
|
+
name
|
638
|
+
original_name
|
639
|
+
owner
|
640
|
+
parameters
|
641
|
+
source_location
|
642
|
+
super_method
|
643
|
+
to_s
|
644
|
+
]
|
645
|
+
).freeze,
|
646
|
+
ZeroDivisionError => hashify(%I[initialize]).freeze,
|
647
|
+
# Modules
|
648
|
+
Kernel => hashify(
|
649
|
+
%I[
|
650
|
+
Array
|
651
|
+
Complex
|
652
|
+
Float
|
653
|
+
Hash
|
654
|
+
Integer
|
655
|
+
Rational
|
656
|
+
String
|
657
|
+
__callee__
|
658
|
+
__dir__
|
659
|
+
__method__
|
660
|
+
autoload?
|
661
|
+
block_given?
|
662
|
+
caller
|
663
|
+
caller_locations
|
664
|
+
catch
|
665
|
+
format
|
666
|
+
global_variables
|
667
|
+
iterator?
|
668
|
+
lambda
|
669
|
+
local_variables
|
670
|
+
loop
|
671
|
+
method
|
672
|
+
methods
|
673
|
+
proc
|
674
|
+
rand
|
675
|
+
!~
|
676
|
+
<=>
|
677
|
+
===
|
678
|
+
=~
|
679
|
+
class
|
680
|
+
clone
|
681
|
+
dup
|
682
|
+
enum_for
|
683
|
+
eql?
|
684
|
+
frozen?
|
685
|
+
hash
|
686
|
+
inspect
|
687
|
+
instance_of?
|
688
|
+
instance_variable_defined?
|
689
|
+
instance_variable_get
|
690
|
+
instance_variables
|
691
|
+
is_a?
|
692
|
+
itself
|
693
|
+
kind_of?
|
694
|
+
nil?
|
695
|
+
object_id
|
696
|
+
private_methods
|
697
|
+
protected_methods
|
698
|
+
public_method
|
699
|
+
public_methods
|
700
|
+
public_send
|
701
|
+
respond_to?
|
702
|
+
respond_to_missing?
|
703
|
+
__send__
|
704
|
+
send
|
705
|
+
singleton_class
|
706
|
+
singleton_method
|
707
|
+
singleton_methods
|
708
|
+
tainted?
|
709
|
+
tap
|
710
|
+
to_enum
|
711
|
+
to_s
|
712
|
+
untrusted?
|
713
|
+
]
|
714
|
+
).freeze
|
715
|
+
}.freeze
|
716
|
+
|
717
|
+
##
|
718
|
+
# @private List of Ruby class methods that the evaluator allows
|
719
|
+
# during expression evaluation
|
720
|
+
RUBY_CLASS_METHOD_WHITELIST = {
|
721
|
+
# Classes
|
722
|
+
Debugger => hashify(%I[allow_mutating_methods!]).freeze
|
723
|
+
}.freeze
|
724
|
+
|
725
|
+
##
|
726
|
+
# @private List of Ruby instance methods that the evaluator allows
|
727
|
+
# during expression evaluation
|
728
|
+
RUBY_INSTANCE_METHOD_WHITELIST = {
|
729
|
+
Evaluator => hashify(%I[allow_mutating_methods!]).freeze
|
730
|
+
}.freeze
|
731
|
+
|
732
|
+
PROHIBITED_OPERATION_MSG = "Prohibited operation detected".freeze
|
733
|
+
MUTATION_DETECTED_MSG = "Mutation detected!".freeze
|
734
|
+
COMPILATION_FAIL_MSG = "Unable to compile expression".freeze
|
735
|
+
LONG_EVAL_MSG = "Evaluation exceeded time limit".freeze
|
736
|
+
|
737
|
+
EVALUATOR_REFERENCE = :__evaluator__
|
738
|
+
|
739
|
+
##
|
740
|
+
# @private
|
741
|
+
# Read-only evaluates a single expression in a given
|
742
|
+
# context binding. Handles any exceptions raised.
|
743
|
+
#
|
744
|
+
# @param [Binding] binding The binding object from the context
|
745
|
+
# @param [String] expression A string of code to be evaluated
|
746
|
+
#
|
747
|
+
# @return [Object] The result Ruby object from evaluating the
|
748
|
+
# expression. If the expression is blocked from mutating
|
749
|
+
# the state of program. A
|
750
|
+
# {Google::Cloud::Debugger::MutationError} will be returned.
|
751
|
+
#
|
752
|
+
def self.readonly_eval_expression binding, expression
|
753
|
+
new.readonly_eval_expression binding, expression
|
754
|
+
end
|
755
|
+
|
756
|
+
##
|
757
|
+
# @private
|
758
|
+
# Returns the evaluator currently running, or nil if an evaluation
|
759
|
+
# is not currently running.
|
760
|
+
#
|
761
|
+
# @return [Evaluator, nil]
|
762
|
+
#
|
763
|
+
def self.current
|
764
|
+
Thread.current.thread_variable_get EVALUATOR_REFERENCE
|
765
|
+
end
|
766
|
+
|
767
|
+
##
|
768
|
+
# Create a new Evaluator.
|
769
|
+
# @private
|
770
|
+
#
|
771
|
+
# @param [boolean, nil] allow_mutating_methods whether to allow
|
772
|
+
# calling of potentially mutating methods, or nil to default to
|
773
|
+
# the current configuration setting.
|
774
|
+
# @param [Numeric, nil] time_limit the time limit in seconds, or nil
|
775
|
+
# to default to the current configuration setting.
|
776
|
+
#
|
777
|
+
def initialize allow_mutating_methods: nil, time_limit: nil
|
778
|
+
@allow_mutating_methods =
|
779
|
+
if allow_mutating_methods.nil?
|
780
|
+
Debugger.configure.allow_mutating_methods
|
781
|
+
else
|
782
|
+
allow_mutating_methods
|
783
|
+
end
|
784
|
+
@time_limit = time_limit ||
|
785
|
+
Debugger.configure.evaluation_time_limit
|
786
|
+
end
|
787
|
+
|
788
|
+
##
|
789
|
+
# Allow calling of mutating methods even if mutation detection is
|
790
|
+
# configured to be active. This may be called only during debugger
|
791
|
+
# condition or expression evaluation.
|
792
|
+
#
|
793
|
+
# If you pass in a block, it will be evaluated with mutation
|
794
|
+
# detection disabled, and the original setting will be restored
|
795
|
+
# afterward. If you do not pass a block, this evaluator will
|
796
|
+
# permanently allow mutating methods.
|
797
|
+
# @private
|
798
|
+
#
|
799
|
+
def allow_mutating_methods!
|
800
|
+
old = @allow_mutating_methods
|
801
|
+
@allow_mutating_methods = true
|
802
|
+
return unless block_given?
|
803
|
+
|
804
|
+
result = yield
|
805
|
+
@allow_mutating_methods = old
|
806
|
+
result
|
807
|
+
end
|
808
|
+
|
809
|
+
##
|
810
|
+
# Read-only evaluates a single expression in a given
|
811
|
+
# context binding. Handles any exceptions raised.
|
812
|
+
# @private
|
813
|
+
#
|
814
|
+
# @param [Binding] binding The binding object from the context
|
815
|
+
# @param [String] expression A string of code to be evaluates
|
816
|
+
#
|
817
|
+
# @return [Object] The result Ruby object from evaluating the
|
818
|
+
# expression. If the expression is blocked from mutating
|
819
|
+
# the state of program. A
|
820
|
+
# {Google::Cloud::Debugger::MutationError} will be returned.
|
821
|
+
#
|
822
|
+
def readonly_eval_expression binding, expression
|
823
|
+
compilation_result = validate_compiled_expression expression
|
824
|
+
return compilation_result if compilation_result.is_a? Exception
|
825
|
+
|
826
|
+
readonly_eval_expression_exec binding, expression
|
827
|
+
rescue StandardError => e
|
828
|
+
"Unable to evaluate expression: #{e.message}"
|
829
|
+
end
|
830
|
+
|
831
|
+
private
|
832
|
+
|
833
|
+
##
|
834
|
+
# @private Actually read-only evaluates an expression in a given
|
835
|
+
# context binding. The evaluation is done in a separate thread due
|
836
|
+
# to this method may be run from Ruby Trace call back, where
|
837
|
+
# addtional code tracing is disabled in original thread.
|
838
|
+
#
|
839
|
+
# @param [Binding] binding The binding object from the context
|
840
|
+
# @param [String] expression A string of code to be evaluates
|
841
|
+
#
|
842
|
+
# @return [Object] The result Ruby object from evaluating the
|
843
|
+
# expression. It returns Google::Cloud::Debugger::MutationError
|
844
|
+
# if a mutation is caught.
|
845
|
+
#
|
846
|
+
def readonly_eval_expression_exec binding, expression
|
847
|
+
# The evaluation is most likely triggered from a trace callback,
|
848
|
+
# where addtional nested tracing is disabled by VM. So we need to
|
849
|
+
# do evaluation in a new thread, where function calls can be
|
850
|
+
# traced.
|
851
|
+
thr = Thread.new do
|
852
|
+
begin
|
853
|
+
Thread.current.thread_variable_set EVALUATOR_REFERENCE, self
|
854
|
+
binding.eval wrap_expression(expression)
|
855
|
+
rescue StandardError, EvaluationError => e
|
856
|
+
# Treat all StandardError as mutation and set @mutation_cause
|
857
|
+
unless e.instance_variable_get :@mutation_cause
|
858
|
+
e.instance_variable_set(
|
859
|
+
:@mutation_cause,
|
860
|
+
Google::Cloud::Debugger::EvaluationError::UNKNOWN_CAUSE
|
861
|
+
)
|
862
|
+
end
|
863
|
+
|
864
|
+
e
|
865
|
+
end
|
866
|
+
end
|
867
|
+
|
868
|
+
thr.join @time_limit
|
869
|
+
|
870
|
+
# Force terminate evaluation thread if not finished already and
|
871
|
+
# return an Exception
|
872
|
+
if thr.alive?
|
873
|
+
thr.kill
|
874
|
+
|
875
|
+
Google::Cloud::Debugger::EvaluationError.new LONG_EVAL_MSG
|
876
|
+
else
|
877
|
+
thr.value
|
878
|
+
end
|
879
|
+
end
|
880
|
+
|
881
|
+
##
|
882
|
+
# @private Compile the expression into YARV instructions. Return
|
883
|
+
# Google::Cloud::Debugger::MutationError if any prohibited YARV
|
884
|
+
# instructions are found.
|
885
|
+
#
|
886
|
+
# @param [String] expression String of code expression
|
887
|
+
#
|
888
|
+
# @return [String,Google::Cloud::Debugger::MutationError] It returns
|
889
|
+
# the compile YARV instructions if no prohibited bytecodes are
|
890
|
+
# found. Otherwise return Google::Cloud::Debugger::MutationError.
|
891
|
+
#
|
892
|
+
def validate_compiled_expression expression
|
893
|
+
begin
|
894
|
+
yarv_instructions =
|
895
|
+
RubyVM::InstructionSequence.compile(expression).disasm
|
896
|
+
rescue ScriptError
|
897
|
+
return Google::Cloud::Debugger::MutationError.new(
|
898
|
+
COMPILATION_FAIL_MSG,
|
899
|
+
Google::Cloud::Debugger::EvaluationError::PROHIBITED_YARV
|
900
|
+
)
|
901
|
+
end
|
902
|
+
|
903
|
+
unless immutable_yarv_instructions? yarv_instructions
|
904
|
+
return Google::Cloud::Debugger::MutationError.new(
|
905
|
+
MUTATION_DETECTED_MSG,
|
906
|
+
Google::Cloud::Debugger::EvaluationError::PROHIBITED_YARV
|
907
|
+
)
|
908
|
+
end
|
909
|
+
|
910
|
+
yarv_instructions
|
911
|
+
end
|
912
|
+
|
913
|
+
##
|
914
|
+
# @private Helps checking if a given set of YARV instructions
|
915
|
+
# contains any prohibited bytecode or instructions.
|
916
|
+
#
|
917
|
+
# @param [String] yarv_instructions Compiled YARV instructions
|
918
|
+
# string
|
919
|
+
# @param [Boolean] allow_localops Whether allows local variable
|
920
|
+
# write operations
|
921
|
+
#
|
922
|
+
# @return [Boolean] True if the YARV instructions don't contain any
|
923
|
+
# prohibited operations. Otherwise false.
|
924
|
+
#
|
925
|
+
def immutable_yarv_instructions? yarv_instructions,
|
926
|
+
allow_localops: false
|
927
|
+
byte_code_blacklist_regex = if allow_localops
|
928
|
+
BYTE_CODE_BLACKLIST_REGEX
|
929
|
+
else
|
930
|
+
FULL_BYTE_CODE_BLACKLIST_REGEX
|
931
|
+
end
|
932
|
+
|
933
|
+
func_call_flag_blacklist_regex = FUNC_CALL_FLAG_BLACKLIST_REGEX
|
934
|
+
|
935
|
+
catch_table_type_blacklist_regex = CATCH_TABLE_BLACKLIST_REGEX
|
936
|
+
|
937
|
+
!(yarv_instructions.match(func_call_flag_blacklist_regex) ||
|
938
|
+
yarv_instructions.match(byte_code_blacklist_regex) ||
|
939
|
+
yarv_instructions.match(catch_table_type_blacklist_regex))
|
940
|
+
end
|
941
|
+
|
942
|
+
##
|
943
|
+
# @private Wraps expression with tracing code
|
944
|
+
def wrap_expression expression
|
945
|
+
"""
|
946
|
+
begin
|
947
|
+
::Google::Cloud::Debugger::Breakpoint::Evaluator.send(
|
948
|
+
:enable_method_trace_for_thread)
|
949
|
+
#{expression}
|
950
|
+
ensure
|
951
|
+
::Google::Cloud::Debugger::Breakpoint::Evaluator.send(
|
952
|
+
:disable_method_trace_for_thread)
|
953
|
+
end
|
954
|
+
"""
|
955
|
+
end
|
956
|
+
|
957
|
+
# rubocop:disable Layout/RescueEnsureAlignment
|
958
|
+
|
959
|
+
##
|
960
|
+
# @private Evaluation tracing callback function. This is called
|
961
|
+
# everytime a Ruby function is called during evaluation of
|
962
|
+
# an expression.
|
963
|
+
#
|
964
|
+
# @param [Object] receiver The receiver of the function being called
|
965
|
+
# @param [Symbol] mid The method name
|
966
|
+
#
|
967
|
+
# @return [NilClass] Nil if no prohibited operations are found.
|
968
|
+
# Otherwise raise Google::Cloud::Debugger::MutationError error.
|
969
|
+
#
|
970
|
+
def trace_func_callback receiver, defined_class, mid
|
971
|
+
return if @allow_mutating_methods
|
972
|
+
if receiver.is_a?(Class) || receiver.is_a?(Module)
|
973
|
+
if whitelisted_ruby_class_method? defined_class, receiver, mid
|
974
|
+
return
|
975
|
+
end
|
976
|
+
elsif whitelisted_ruby_instance_method? defined_class, mid
|
977
|
+
return
|
978
|
+
end
|
979
|
+
|
980
|
+
yarv_instructions = begin
|
981
|
+
RubyVM::InstructionSequence.disasm receiver.method mid
|
982
|
+
rescue StandardError
|
983
|
+
raise Google::Cloud::Debugger::EvaluationError.new(
|
984
|
+
PROHIBITED_OPERATION_MSG,
|
985
|
+
Google::Cloud::Debugger::EvaluationError::META_PROGRAMMING
|
986
|
+
)
|
987
|
+
end
|
988
|
+
|
989
|
+
return if immutable_yarv_instructions?(yarv_instructions,
|
990
|
+
allow_localops: true)
|
991
|
+
raise Google::Cloud::Debugger::MutationError.new(
|
992
|
+
MUTATION_DETECTED_MSG,
|
993
|
+
Google::Cloud::Debugger::EvaluationError::PROHIBITED_YARV
|
994
|
+
)
|
995
|
+
end
|
996
|
+
|
997
|
+
# rubocop:enable Layout/RescueEnsureAlignment
|
998
|
+
|
999
|
+
##
|
1000
|
+
# @private Evaluation tracing callback function. This is called
|
1001
|
+
# everytime a C function is called during evaluation of
|
1002
|
+
# an expression.
|
1003
|
+
#
|
1004
|
+
# @param [Object] receiver The receiver of the function being called
|
1005
|
+
# @param [Class] defined_class The Class of where the function is
|
1006
|
+
# defined
|
1007
|
+
# @param [Symbol] mid The method name
|
1008
|
+
#
|
1009
|
+
# @return [NilClass] Nil if no prohibited operations are found.
|
1010
|
+
# Otherwise raise Google::Cloud::Debugger::MutationError error.
|
1011
|
+
#
|
1012
|
+
def trace_c_func_callback receiver, defined_class, mid
|
1013
|
+
return if @allow_mutating_methods
|
1014
|
+
if receiver.is_a?(Class) || receiver.is_a?(Module)
|
1015
|
+
invalid_op =
|
1016
|
+
!validate_c_class_method(defined_class, receiver, mid)
|
1017
|
+
else
|
1018
|
+
invalid_op = !validate_c_instance_method(defined_class, mid)
|
1019
|
+
end
|
1020
|
+
|
1021
|
+
return unless invalid_op
|
1022
|
+
|
1023
|
+
Google::Cloud::Debugger::Breakpoint::Evaluator.send(
|
1024
|
+
:disable_method_trace_for_thread
|
1025
|
+
)
|
1026
|
+
raise Google::Cloud::Debugger::MutationError.new(
|
1027
|
+
PROHIBITED_OPERATION_MSG,
|
1028
|
+
Google::Cloud::Debugger::EvaluationError::PROHIBITED_C_FUNC
|
1029
|
+
)
|
1030
|
+
end
|
1031
|
+
|
1032
|
+
##
|
1033
|
+
# @private Helper method to verify whether a C level class method
|
1034
|
+
# is allowed or not.
|
1035
|
+
def validate_c_class_method klass, receiver, mid
|
1036
|
+
IMMUTABLE_CLASSES.include?(receiver) ||
|
1037
|
+
(C_CLASS_METHOD_WHITELIST[receiver] || {})[mid] ||
|
1038
|
+
(C_INSTANCE_METHOD_WHITELIST[klass] || {})[mid]
|
1039
|
+
end
|
1040
|
+
|
1041
|
+
##
|
1042
|
+
# @private Helper method to verify whether a C level instance method
|
1043
|
+
# is allowed or not.
|
1044
|
+
def validate_c_instance_method klass, mid
|
1045
|
+
IMMUTABLE_CLASSES.include?(klass) ||
|
1046
|
+
(C_INSTANCE_METHOD_WHITELIST[klass] || {})[mid]
|
1047
|
+
end
|
1048
|
+
|
1049
|
+
##
|
1050
|
+
# @private Helper method to verify whether a ruby class method
|
1051
|
+
# is allowed or not.
|
1052
|
+
def whitelisted_ruby_class_method? klass, receiver, mid
|
1053
|
+
IMMUTABLE_CLASSES.include?(klass) ||
|
1054
|
+
(RUBY_CLASS_METHOD_WHITELIST[receiver] || {})[mid] ||
|
1055
|
+
(RUBY_INSTANCE_METHOD_WHITELIST[klass] || {})[mid]
|
1056
|
+
end
|
1057
|
+
|
1058
|
+
##
|
1059
|
+
# @private Helper method to verify whether a ruby instance method
|
1060
|
+
# is allowed or not.
|
1061
|
+
def whitelisted_ruby_instance_method? klass, mid
|
1062
|
+
IMMUTABLE_CLASSES.include?(klass) ||
|
1063
|
+
(RUBY_INSTANCE_METHOD_WHITELIST[klass] || {})[mid]
|
1064
|
+
end
|
1065
|
+
end
|
1066
|
+
end
|
1067
|
+
|
1068
|
+
##
|
1069
|
+
# @private Custom error type used to identify evaluation error during
|
1070
|
+
# breakpoint expression evaluation.
|
1071
|
+
class EvaluationError < Exception
|
1072
|
+
UNKNOWN_CAUSE = :unknown_cause
|
1073
|
+
PROHIBITED_YARV = :prohibited_yarv
|
1074
|
+
PROHIBITED_C_FUNC = :prohibited_c_func
|
1075
|
+
META_PROGRAMMING = :meta_programming
|
1076
|
+
|
1077
|
+
attr_reader :mutation_cause
|
1078
|
+
|
1079
|
+
def initialize msg = nil, mutation_cause = UNKNOWN_CAUSE
|
1080
|
+
@mutation_cause = mutation_cause
|
1081
|
+
super msg
|
1082
|
+
end
|
1083
|
+
|
1084
|
+
def inspect
|
1085
|
+
"#<#{self.class}: #{message}>"
|
1086
|
+
end
|
1087
|
+
end
|
1088
|
+
|
1089
|
+
##
|
1090
|
+
# @private Custom error type used to identify mutation during breakpoint
|
1091
|
+
# expression evaluations
|
1092
|
+
class MutationError < EvaluationError
|
1093
|
+
def initialize msg = "Mutation detected!", *args
|
1094
|
+
super
|
1095
|
+
end
|
1096
|
+
end
|
1097
|
+
end
|
1098
|
+
end
|
1099
|
+
end
|