google-cloud-debugger 0.40.0

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