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.
Files changed (45) hide show
  1. checksums.yaml +7 -0
  2. data/.yardopts +18 -0
  3. data/AUTHENTICATION.md +178 -0
  4. data/CHANGELOG.md +233 -0
  5. data/CODE_OF_CONDUCT.md +40 -0
  6. data/CONTRIBUTING.md +188 -0
  7. data/INSTRUMENTATION.md +115 -0
  8. data/LICENSE +201 -0
  9. data/LOGGING.md +32 -0
  10. data/OVERVIEW.md +266 -0
  11. data/TROUBLESHOOTING.md +31 -0
  12. data/ext/google/cloud/debugger/debugger_c/debugger.c +31 -0
  13. data/ext/google/cloud/debugger/debugger_c/debugger.h +26 -0
  14. data/ext/google/cloud/debugger/debugger_c/evaluator.c +115 -0
  15. data/ext/google/cloud/debugger/debugger_c/evaluator.h +25 -0
  16. data/ext/google/cloud/debugger/debugger_c/extconf.rb +22 -0
  17. data/ext/google/cloud/debugger/debugger_c/tracer.c +542 -0
  18. data/ext/google/cloud/debugger/debugger_c/tracer.h +25 -0
  19. data/lib/google-cloud-debugger.rb +181 -0
  20. data/lib/google/cloud/debugger.rb +259 -0
  21. data/lib/google/cloud/debugger/agent.rb +255 -0
  22. data/lib/google/cloud/debugger/backoff.rb +70 -0
  23. data/lib/google/cloud/debugger/breakpoint.rb +443 -0
  24. data/lib/google/cloud/debugger/breakpoint/evaluator.rb +1099 -0
  25. data/lib/google/cloud/debugger/breakpoint/source_location.rb +74 -0
  26. data/lib/google/cloud/debugger/breakpoint/stack_frame.rb +109 -0
  27. data/lib/google/cloud/debugger/breakpoint/status_message.rb +93 -0
  28. data/lib/google/cloud/debugger/breakpoint/validator.rb +92 -0
  29. data/lib/google/cloud/debugger/breakpoint/variable.rb +595 -0
  30. data/lib/google/cloud/debugger/breakpoint/variable_table.rb +96 -0
  31. data/lib/google/cloud/debugger/breakpoint_manager.rb +311 -0
  32. data/lib/google/cloud/debugger/credentials.rb +50 -0
  33. data/lib/google/cloud/debugger/debuggee.rb +222 -0
  34. data/lib/google/cloud/debugger/debuggee/app_uniquifier_generator.rb +76 -0
  35. data/lib/google/cloud/debugger/logpoint.rb +98 -0
  36. data/lib/google/cloud/debugger/middleware.rb +200 -0
  37. data/lib/google/cloud/debugger/project.rb +110 -0
  38. data/lib/google/cloud/debugger/rails.rb +174 -0
  39. data/lib/google/cloud/debugger/request_quota_manager.rb +95 -0
  40. data/lib/google/cloud/debugger/service.rb +88 -0
  41. data/lib/google/cloud/debugger/snappoint.rb +208 -0
  42. data/lib/google/cloud/debugger/tracer.rb +137 -0
  43. data/lib/google/cloud/debugger/transmitter.rb +199 -0
  44. data/lib/google/cloud/debugger/version.rb +22 -0
  45. 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