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.
- checksums.yaml +4 -4
- data/ext/google/cloud/debugger/debugger_c/evaluator.c +8 -3
- data/ext/google/cloud/debugger/debugger_c/tracer.c +3 -13
- data/lib/google/cloud/debugger/agent.rb +21 -3
- data/lib/google/cloud/debugger/breakpoint.rb +110 -107
- data/lib/google/cloud/debugger/breakpoint/evaluator.rb +49 -162
- data/lib/google/cloud/debugger/breakpoint/status_message.rb +95 -0
- data/lib/google/cloud/debugger/breakpoint/validator.rb +91 -0
- data/lib/google/cloud/debugger/breakpoint/variable.rb +313 -41
- data/lib/google/cloud/debugger/breakpoint/variable_table.rb +96 -0
- data/lib/google/cloud/debugger/breakpoint_manager.rb +45 -10
- data/lib/google/cloud/debugger/credentials.rb +2 -1
- data/lib/google/cloud/debugger/logpoint.rb +97 -0
- data/lib/google/cloud/debugger/middleware.rb +16 -5
- data/lib/google/cloud/debugger/request_quota_manager.rb +95 -0
- data/lib/google/cloud/debugger/snappoint.rb +208 -0
- data/lib/google/cloud/debugger/tracer.rb +20 -32
- data/lib/google/cloud/debugger/version.rb +1 -1
- metadata +8 -2
@@ -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
|
-
|
777
|
-
|
778
|
-
|
779
|
-
|
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
|
-
|
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.
|
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
|
-
|
868
|
-
|
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
|
-
|
905
|
-
|
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
|
-
#
|
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::
|
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
|
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
|
-
|
966
|
-
Google::Cloud::Debugger::
|
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
|
-
|
973
|
-
Google::Cloud::Debugger::
|
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
|
-
|
1065
|
-
Google::Cloud::Debugger::
|
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
|
-
|
1095
|
-
Google::Cloud::Debugger::
|
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
|
1120
|
-
# expression
|
1121
|
-
class
|
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 =
|
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
|
-
"
|
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
|