google-cloud-debugger 0.26.1 → 0.27.0

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