google-cloud-debugger 0.26.1 → 0.27.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.
@@ -38,10 +38,6 @@ module Google
38
38
  # for details.
39
39
  #
40
40
  module Evaluator
41
- ##
42
- # Max number of top stacks to collect local variables information
43
- STACK_EVAL_DEPTH = 5
44
-
45
41
  ##
46
42
  # @private YARV bytecode that the evaluator blocks during expression
47
43
  # evaluation. If the breakpoint contains expressions that uses the
@@ -773,85 +769,14 @@ module Google
773
769
  }).freeze
774
770
  }.freeze
775
771
 
776
- class << self
777
- ##
778
- # Evaluates call stack. Collects function name and location of each
779
- # frame from given binding objects. Collects local variable
780
- # information from top frames.
781
- #
782
- # @param [Array<Binding>] call_stack_bindings A list of binding
783
- # objects that come from each of the call stack frames.
784
- # @return [Array<Google::Cloud::Debugger::Breakpoint::StackFrame>]
785
- # A list of StackFrame objects that represent state of the
786
- # call stack
787
- #
788
- def eval_call_stack call_stack_bindings
789
- result = []
790
- call_stack_bindings.each_with_index do |frame_binding, i|
791
- frame_info = StackFrame.new.tap do |sf|
792
- sf.function = frame_binding.eval("__method__").to_s
793
- sf.location = SourceLocation.new.tap do |l|
794
- l.path =
795
- frame_binding.eval("::File.absolute_path(__FILE__)")
796
- l.line = frame_binding.eval("__LINE__")
797
- end
798
- end
799
-
800
- if i < STACK_EVAL_DEPTH
801
- frame_info.locals = eval_frame_variables frame_binding
802
- end
772
+ PROHIBITED_OPERATION_MSG = "Prohibited operation detected".freeze
773
+ MUTATION_DETECTED_MSG = "Mutation detected!".freeze
774
+ COMPILATION_FAIL_MSG = "Unable to compile expression".freeze
775
+ LONG_EVAL_MSG = "Evaluation exceeded time limit".freeze
803
776
 
804
- result << frame_info
805
- end
806
-
807
- result
808
- end
809
-
810
- ##
811
- # Evaluates a boolean conditional expression in the given context
812
- # binding. The evaluation subjects to the read-only rules. If
813
- # the expression does any write operation, the evaluation aborts
814
- # and returns false.
815
- #
816
- # @param [Binding] binding The binding object from the context
817
- # @param [String] condition A string of code to be evaluates
818
- #
819
- # @return [Boolean] True if condition expression read-only evaluates
820
- # to true. Otherwise false.
821
- #
822
- def eval_condition binding, condition
823
- result = readonly_eval_expression_exec binding, condition
824
-
825
- if result.is_a?(Exception) &&
826
- result.instance_variable_get(:@mutation_cause)
827
- return false
828
- end
829
-
830
- result ? true : false
831
- end
832
-
833
- ##
834
- # Evaluates the breakpoint expressions at the point that triggered
835
- # the breakpoint. The expressions subject to the read-only rules.
836
- # If the expressions do any write operations, the evaluations abort
837
- # and show an error message in place of the real result.
838
- #
839
- # @param [Binding] binding The binding object from the context
840
- # @param [Array<String>] expressions A list of code strings to be
841
- # evaluated
842
- # @return [Array<Google::Cloud::Debugger::Breakpoint::Variable>]
843
- # A list of Breakpoint::Variables objects that represent the
844
- # expression evaluations results.
845
- #
846
- def eval_expressions binding, expressions
847
- expressions.map do |expression|
848
- eval_result = readonly_eval_expression binding, expression
849
- evaluated_var = Variable.from_rb_var eval_result
850
- evaluated_var.name = expression
851
- evaluated_var
852
- end
853
- end
777
+ EXPRESSION_EVALUATION_TIME_THRESHOLD = 0.05
854
778
 
779
+ class << self
855
780
  ##
856
781
  # @private Read-only evaluates a single expression in a given
857
782
  # context binding. Handles any exceptions raised.
@@ -861,48 +786,16 @@ module Google
861
786
  #
862
787
  # @return [Object] The result Ruby object from evaluating the
863
788
  # expression. If the expression is blocked from mutating
864
- # the state of program. An error message is returned instead.
789
+ # the state of program. A
790
+ # {Google::Cloud::Debugger::MutationError} will be returned.
865
791
  #
866
792
  def readonly_eval_expression binding, expression
867
- begin
868
- result = readonly_eval_expression_exec binding, expression
869
- rescue => e
870
- result = "Unable to evaluate expression: #{e.message}"
871
- end
872
-
873
- if result.is_a?(Exception) &&
874
- result.instance_variable_get(:@mutation_cause)
875
- return "Error: #{result.message}"
876
- end
877
-
878
- result
879
- end
880
-
881
- ##
882
- # Format log message by interpolate expressions.
883
- #
884
- # @example
885
- # Evaluator.format_log_message("Hello $0",
886
- # ["World"]) #=> "Hello World"
887
- #
888
- # @param [String] message_format The message with with
889
- # expression placeholders such as `$0`, `$1`, etc.
890
- # @param [Array<Google::Cloud::Debugger::Breakpoint::Variable>]
891
- # expressions An array of evaluated expression variables to be
892
- # placed into message_format's placeholders. The variables need
893
- # to have type equal String.
894
- #
895
- # @return [String] The formatted message string
896
- #
897
- def format_message message_format, expressions
898
- # Substitute placeholders with expressions
899
- message = message_format.gsub(/(?<!\$)\$\d+/) do |placeholder|
900
- index = placeholder.match(/\$(\d+)/)[1].to_i
901
- index < expressions.size ? expressions[index].inspect : ""
902
- end
793
+ compilation_result = validate_compiled_expression expression
794
+ return compilation_result if compilation_result.is_a?(Exception)
903
795
 
904
- # Unescape "$" charactors
905
- message.gsub(/\$\$/, "$")
796
+ readonly_eval_expression_exec binding, expression
797
+ rescue => e
798
+ "Unable to evaluate expression: #{e.message}"
906
799
  end
907
800
 
908
801
  private
@@ -921,9 +814,6 @@ module Google
921
814
  # if a mutation is caught.
922
815
  #
923
816
  def readonly_eval_expression_exec binding, expression
924
- compilation_result = validate_compiled_expression expression
925
- return compilation_result if compilation_result.is_a?(Exception)
926
-
927
817
  # The evaluation is most likely triggered from a trace callback,
928
818
  # where addtional nested tracing is disabled by VM. So we need to
929
819
  # do evaluation in a new thread, where function calls can be
@@ -932,17 +822,28 @@ module Google
932
822
  begin
933
823
  binding.eval wrap_expression(expression)
934
824
  rescue => e
935
- # Threat all StandardError as mutation and set @mutation_cause
825
+ # Treat all StandardError as mutation and set @mutation_cause
936
826
  unless e.instance_variable_get :@mutation_cause
937
827
  e.instance_variable_set(
938
828
  :@mutation_cause,
939
- Google::Cloud::Debugger::MutationError::UNKNOWN_CAUSE)
829
+ Google::Cloud::Debugger::EvaluationError::UNKNOWN_CAUSE)
940
830
  end
831
+
941
832
  e
942
833
  end
943
834
  end
944
835
 
945
- thr.join.value
836
+ thr.join EXPRESSION_EVALUATION_TIME_THRESHOLD
837
+
838
+ # Force terminate evaluation thread if not finished already and
839
+ # return an Exception
840
+ if thr.alive?
841
+ thr.kill
842
+
843
+ Google::Cloud::Debugger::EvaluationError.new LONG_EVAL_MSG
844
+ else
845
+ thr.value
846
+ end
946
847
  end
947
848
 
948
849
  ##
@@ -962,43 +863,21 @@ module Google
962
863
  RubyVM::InstructionSequence.compile(expression).disasm
963
864
  rescue ScriptError
964
865
  return Google::Cloud::Debugger::MutationError.new(
965
- "Unable to compile expression",
966
- Google::Cloud::Debugger::MutationError::PROHIBITED_YARV
866
+ COMPILATION_FAIL_MSG,
867
+ Google::Cloud::Debugger::EvaluationError::PROHIBITED_YARV
967
868
  )
968
869
  end
969
870
 
970
871
  unless immutable_yarv_instructions? yarv_instructions
971
872
  return Google::Cloud::Debugger::MutationError.new(
972
- "Mutation detected!",
973
- Google::Cloud::Debugger::MutationError::PROHIBITED_YARV
873
+ MUTATION_DETECTED_MSG,
874
+ Google::Cloud::Debugger::EvaluationError::PROHIBITED_YARV
974
875
  )
975
876
  end
976
877
 
977
878
  yarv_instructions
978
879
  end
979
880
 
980
- ##
981
- # @private Helps evaluating local variables from a single frame
982
- # binding
983
- #
984
- # @param [Binding] frame_binding The context binding object from
985
- # a given frame.
986
- # @return [Array<Google::Cloud::Debugger::Variable>] A list of
987
- # Breakpoint::Variables that represent all the local variables
988
- # in a context frame.
989
- #
990
- def eval_frame_variables frame_binding
991
- result_variables = []
992
- result_variables +=
993
- frame_binding.local_variables.map do |local_var_name|
994
- local_var = frame_binding.local_variable_get(local_var_name)
995
-
996
- Variable.from_rb_var(local_var, name: local_var_name)
997
- end
998
-
999
- result_variables
1000
- end
1001
-
1002
881
  ##
1003
882
  # @private Helps checking if a given set of YARV instructions
1004
883
  # contains any prohibited bytecode or instructions.
@@ -1061,8 +940,8 @@ module Google
1061
940
  return if immutable_yarv_instructions?(yarv_instructions,
1062
941
  allow_localops: true)
1063
942
  fail Google::Cloud::Debugger::MutationError.new(
1064
- "Mutation detected!",
1065
- Google::Cloud::Debugger::MutationError::PROHIBITED_YARV)
943
+ MUTATION_DETECTED_MSG,
944
+ Google::Cloud::Debugger::EvaluationError::PROHIBITED_YARV)
1066
945
  end
1067
946
 
1068
947
  ##
@@ -1091,8 +970,8 @@ module Google
1091
970
  Google::Cloud::Debugger::Breakpoint::Evaluator.send(
1092
971
  :disable_method_trace_for_thread)
1093
972
  fail Google::Cloud::Debugger::MutationError.new(
1094
- "Invalid operation detected",
1095
- Google::Cloud::Debugger::MutationError::PROHIBITED_C_FUNC)
973
+ PROHIBITED_OPERATION_MSG,
974
+ Google::Cloud::Debugger::EvaluationError::PROHIBITED_C_FUNC)
1096
975
  end
1097
976
 
1098
977
  ##
@@ -1116,23 +995,31 @@ module Google
1116
995
  end
1117
996
 
1118
997
  ##
1119
- # @private Custom error type used to identify mutation during breakpoint
1120
- # expression evaluations
1121
- class MutationError < StandardError
998
+ # @private Custom error type used to identify evaluation error during
999
+ # breakpoint expression evaluation.
1000
+ class EvaluationError < StandardError
1122
1001
  UNKNOWN_CAUSE = Object.new.freeze
1123
1002
  PROHIBITED_YARV = Object.new.freeze
1124
1003
  PROHIBITED_C_FUNC = Object.new.freeze
1125
1004
 
1126
1005
  attr_reader :mutation_cause
1127
1006
 
1128
- def initialize msg = "Mutation detected!",
1129
- mutation_cause = UNKNOWN_CAUSE
1007
+ def initialize msg = nil, mutation_cause = UNKNOWN_CAUSE
1130
1008
  @mutation_cause = mutation_cause
1131
1009
  super(msg)
1132
1010
  end
1133
1011
 
1134
1012
  def inspect
1135
- "#<MutationError: #{message}>"
1013
+ "#<#{self.class}: #{message}>"
1014
+ end
1015
+ end
1016
+
1017
+ ##
1018
+ # @private Custom error type used to identify mutation during breakpoint
1019
+ # expression evaluations
1020
+ class MutationError < EvaluationError
1021
+ def initialize msg = "Mutation detected!", *args
1022
+ super
1136
1023
  end
1137
1024
  end
1138
1025
  end
@@ -0,0 +1,95 @@
1
+ # Copyright 2017 Google Inc. All rights reserved.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+
16
+ module Google
17
+ module Cloud
18
+ module Debugger
19
+ class Breakpoint
20
+ ##
21
+ # # StatusMessage
22
+ #
23
+ # Represents a contextual status message. The message can indicate an
24
+ # error or informational status, and refer to specific parts of the
25
+ # containing object. For example, the Breakpoint.status field can
26
+ # indicate an error referring to the BREAKPOINT_SOURCE_LOCATION with
27
+ # the message Location not found.
28
+ class StatusMessage
29
+ ##
30
+ # Constants used as references to which the message applies.
31
+ UNSPECIFIED = :UNSPECIFIED
32
+ BREAKPOINT_SOURCE_LOCATION = :BREAKPOINT_SOURCE_LOCATION
33
+ BREAKPOINT_CONDITION = :BREAKPOINT_CONDITION
34
+ BREAKPOINT_EXPRESSION = :BREAKPOINT_EXPRESSION
35
+ BREAKPOINT_AGE = :BREAKPOINT_AGE
36
+ VARIABLE_NAME = :VARIABLE_NAME
37
+ VARIABLE_VALUE = :VARIABLE_VALUE
38
+
39
+ ##
40
+ # Distinguishes errors from informational messages.
41
+ attr_accessor :is_error
42
+
43
+ ##
44
+ # Reference to which the message applies.
45
+ attr_accessor :refers_to
46
+
47
+ ##
48
+ # Status message text.
49
+ attr_accessor :description
50
+
51
+ ##
52
+ # New Google::Cloud::Debugger::Breakpoint::StatusMessage
53
+ # from a Google::Devtools::Clouddebugger::V2::StatusMessage object.
54
+ def self.from_grpc grpc
55
+ return nil if grpc.nil?
56
+ new.tap do |s|
57
+ s.is_error = grpc.is_error
58
+ s.refers_to = grpc.refers_to
59
+ s.description = grpc.description.format
60
+ end
61
+ end
62
+
63
+ ##
64
+ # @private Construct a new StatusMessage instance.
65
+ def initialize
66
+ @refers_to = UNSPECIFIED
67
+ end
68
+
69
+ ##
70
+ # @private Determines if the Variable has any data.
71
+ def empty?
72
+ is_error.nil? &&
73
+ refers_to.nil? &&
74
+ description.nil?
75
+ end
76
+
77
+ ##
78
+ # Exports the StatusMessage to a
79
+ # Google::Devtools::Clouddebugger::V2::StatusMessage object.
80
+ def to_grpc
81
+ return nil if empty?
82
+ description_grpc =
83
+ Google::Devtools::Clouddebugger::V2::FormatMessage.new \
84
+ format: description.to_s
85
+
86
+ Google::Devtools::Clouddebugger::V2::StatusMessage.new \
87
+ is_error: true,
88
+ refers_to: refers_to,
89
+ description: description_grpc
90
+ end
91
+ end
92
+ end
93
+ end
94
+ end
95
+ end
@@ -0,0 +1,91 @@
1
+ # Copyright 2017 Google Inc. All rights reserved.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+
16
+ module Google
17
+ module Cloud
18
+ module Debugger
19
+ class Breakpoint
20
+ ##
21
+ # # Validator
22
+ #
23
+ # A collection of static methods to help validate a given breakpoint.
24
+ module Validator
25
+ FILE_NOT_FOUND_MSG = "File not found."
26
+ WRONG_FILE_TYPE_MSG = "File must be a `.rb` file."
27
+ INVALID_LINE_MSG = "Invalid line."
28
+
29
+ ##
30
+ # Validate a given breakpoint. Set breakpoint to error state if
31
+ # the breakpoint fails validation.
32
+ def self.validate breakpoint
33
+ error_msg = nil
34
+ if !verify_file_path breakpoint.full_path
35
+ error_msg = FILE_NOT_FOUND_MSG
36
+ elsif !verify_file_type breakpoint.full_path
37
+ error_msg = WRONG_FILE_TYPE_MSG
38
+ elsif !verify_line breakpoint.full_path, breakpoint.line
39
+ error_msg = INVALID_LINE_MSG
40
+ end
41
+
42
+ if error_msg
43
+ cause = Breakpoint::StatusMessage::BREAKPOINT_SOURCE_LOCATION
44
+ breakpoint.set_error_state error_msg, refers_to: cause
45
+ false
46
+ else
47
+ true
48
+ end
49
+ end
50
+
51
+ ##
52
+ # @private Verifies the given file path exists
53
+ def self.verify_file_path file_path
54
+ File.exist? file_path
55
+ rescue
56
+ false
57
+ end
58
+
59
+ ##
60
+ # @private Check the file from given file path is a ".rb" file
61
+ def self.verify_file_type file_path
62
+ File.extname(file_path) == ".rb"
63
+ rescue
64
+ false
65
+ end
66
+
67
+ ##
68
+ # @private Verifies the given line from a Ruby
69
+ def self.verify_line file_path, line
70
+ file = File.open file_path, "r"
71
+
72
+ # Skip through lines from beginning
73
+ file.gets while file.lineno < line - 1 && !file.eof?
74
+
75
+ line = file.gets
76
+
77
+ # Make sure we have a line (not eof)
78
+ return false unless line
79
+
80
+ blank_line = line =~ /^\s*$/
81
+ comment_line = line =~ /^\s*#.*$/
82
+
83
+ blank_line || comment_line ? false : true
84
+ rescue
85
+ false
86
+ end
87
+ end
88
+ end
89
+ end
90
+ end
91
+ end