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.
- 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
|