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,74 @@
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
+ module Google
17
+ module Cloud
18
+ module Debugger
19
+ class Breakpoint
20
+ ##
21
+ # # SourceLocation
22
+ #
23
+ # Additional information about the source code location that's
24
+ # associated with the breakpoint.
25
+ #
26
+ # See also {Breakpoint#location}.
27
+ #
28
+ class SourceLocation
29
+ ##
30
+ # Path to the source file within the source context of the target
31
+ # binary.
32
+ attr_accessor :path
33
+
34
+ ##
35
+ # Line inside the file. The first line in the file has the value 1.
36
+ attr_accessor :line
37
+
38
+ ##
39
+ # @private Create an empty SourceLocation object.
40
+ def initialize; end
41
+
42
+ ##
43
+ # @private New Google::Cloud::Debugger::Breakpoint::SourceLocation
44
+ # from a Google::Cloud::Debugger::V2::SourceLocation object.
45
+ def self.from_grpc grpc
46
+ return new if grpc.nil?
47
+ new.tap do |o|
48
+ o.path = grpc.path
49
+ o.line = grpc.line
50
+ end
51
+ end
52
+
53
+ ##
54
+ # @private Determines if the SourceLocation has any data.
55
+ def empty?
56
+ path.nil? &&
57
+ line.nil?
58
+ end
59
+
60
+ ##
61
+ # @private Exports the SourceLocation to a
62
+ # Google::Cloud::Debugger::V2::SourceLocation object.
63
+ def to_grpc
64
+ return nil if empty?
65
+ Google::Cloud::Debugger::V2::SourceLocation.new(
66
+ path: path.to_s,
67
+ line: line
68
+ )
69
+ end
70
+ end
71
+ end
72
+ end
73
+ end
74
+ end
@@ -0,0 +1,109 @@
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
+
18
+ module Google
19
+ module Cloud
20
+ module Debugger
21
+ class Breakpoint
22
+ ##
23
+ # # StackFrame
24
+ #
25
+ # Represents a stack frame context.
26
+ #
27
+ # See also {Breakpoint#stack_frames}.
28
+ #
29
+ class StackFrame
30
+ ##
31
+ # Demangled function name at the call site.
32
+ attr_accessor :function
33
+
34
+ ##
35
+ # Source location of the call site.
36
+ attr_accessor :location
37
+
38
+ ##
39
+ # Set of arguments passed to this function. Note that this might not
40
+ # be populated for all stack frames.
41
+ attr_accessor :arguments
42
+
43
+ ##
44
+ # Set of local variables at the stack frame location. Note that this
45
+ # might not be populated for all stack frames.
46
+ attr_accessor :locals
47
+
48
+ ##
49
+ # @private Create an empty StackFrame object.
50
+ def initialize
51
+ @location = SourceLocation.new
52
+ @arguments = []
53
+ @locals = []
54
+ end
55
+
56
+ ##
57
+ # @private New Google::Cloud::Debugger::Breakpoint::SourceLocation
58
+ # from a Google::Cloud::Debugger::V2::SourceLocation object.
59
+ def self.from_grpc grpc
60
+ new.tap do |o|
61
+ o.function = grpc.function
62
+ o.location = SourceLocation.from_grpc grpc.location
63
+ o.arguments = Variable.from_grpc_list grpc.arguments
64
+ o.locals = Variable.from_grpc_list grpc.locals
65
+ end
66
+ end
67
+
68
+ ##
69
+ # @private Determines if the StackFrame has any data.
70
+ def empty?
71
+ function.nil? &&
72
+ location.nil? &&
73
+ arguments.nil? &&
74
+ locals.nil?
75
+ end
76
+
77
+ ##
78
+ # @private Exports the StackFrame to a
79
+ # Google::Cloud::Debugger::V2::StackFrame object.
80
+ def to_grpc
81
+ return nil if empty?
82
+ Google::Cloud::Debugger::V2::StackFrame.new(
83
+ function: function.to_s,
84
+ location: location.to_grpc,
85
+ arguments: arguments_to_grpc,
86
+ locals: locals_to_grpc
87
+ )
88
+ end
89
+
90
+ private
91
+
92
+ ##
93
+ # @private Exports the StackFrame arguments to an array of
94
+ # Google::Cloud::Debugger::V2::Variable objects.
95
+ def arguments_to_grpc
96
+ arguments.nil? ? [] : arguments.map(&:to_grpc)
97
+ end
98
+
99
+ ##
100
+ # @private Exports the StackFrame locals to an array of
101
+ # Google::Cloud::Debugger::V2::Variable objects.
102
+ def locals_to_grpc
103
+ locals.nil? ? [] : locals.map(&:to_grpc)
104
+ end
105
+ end
106
+ end
107
+ end
108
+ end
109
+ end
@@ -0,0 +1,93 @@
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
+ 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::Cloud::Debugger::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::Cloud::Debugger::V2::StatusMessage object.
80
+ def to_grpc
81
+ return nil if empty?
82
+ description_grpc = Google::Cloud::Debugger::V2::FormatMessage.new format: description.to_s
83
+
84
+ Google::Cloud::Debugger::V2::StatusMessage.new \
85
+ is_error: true,
86
+ refers_to: refers_to,
87
+ description: description_grpc
88
+ end
89
+ end
90
+ end
91
+ end
92
+ end
93
+ end
@@ -0,0 +1,92 @@
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
+ 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.".freeze
26
+ WRONG_FILE_TYPE_MSG = "File must be a `.rb` file.".freeze
27
+ INVALID_LINE_MSG = "Invalid line.".freeze
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 StandardError
56
+ false
57
+ end
58
+
59
+ ##
60
+ # @private Check the file from given file path is a Ruby file
61
+ def self.verify_file_type file_path
62
+ File.extname(file_path) == ".rb" ||
63
+ File.basename(file_path) == "config.ru"
64
+ rescue StandardError
65
+ false
66
+ end
67
+
68
+ ##
69
+ # @private Verifies the given line from a Ruby
70
+ def self.verify_line file_path, line
71
+ file = File.open file_path, "r"
72
+
73
+ # Skip through lines from beginning
74
+ file.gets while file.lineno < line - 1 && !file.eof?
75
+
76
+ line = file.gets
77
+
78
+ # Make sure we have a line (not eof)
79
+ return false unless line
80
+
81
+ blank_line = line =~ /^\s*$/
82
+ comment_line = line =~ /^\s*#.*$/
83
+
84
+ blank_line || comment_line ? false : true
85
+ rescue StandardError
86
+ false
87
+ end
88
+ end
89
+ end
90
+ end
91
+ end
92
+ end
@@ -0,0 +1,595 @@
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/status_message"
17
+
18
+ module Google
19
+ module Cloud
20
+ module Debugger
21
+ class Breakpoint
22
+ ##
23
+ # # Variable
24
+ #
25
+ # Represents a variable or an argument possibly of a compound object
26
+ # type. Note how the following variables are represented:
27
+ #
28
+ # A simple Variable:
29
+ # ```ruby
30
+ # x = 5
31
+ # # Captured variable:
32
+ # # { name: "x", value: "5", type: "Integer" }
33
+ # ```
34
+ #
35
+ # A Compound Variable:
36
+ # ```ruby
37
+ # class T
38
+ # attr_accessor :m1, :m2
39
+ # ...
40
+ # end
41
+ # v = T.new(1, "2")
42
+ # # Captured variable:
43
+ # # {
44
+ # # name: "v",
45
+ # # type: "T",
46
+ # # members: [
47
+ # # { name: "@m1", value: "1", type: "Integer" },
48
+ # # { name: "@m2", value: "2", type: "String" }
49
+ # # ]
50
+ # # }
51
+ # ```
52
+ #
53
+ # A Hash object:
54
+ # ```ruby
55
+ # hash = { a: 1, b: :two }
56
+ # # Captured variable:
57
+ # # {
58
+ # # name: "hash",
59
+ # # type: "Hash",
60
+ # # members: [
61
+ # # { name: "a", value: "1", type: "Integer" },
62
+ # # { name: "b", value: ":2", type: "Symbol" }
63
+ # # ]
64
+ # # }
65
+ # ```
66
+ #
67
+ # An Array object:
68
+ # ```ruby
69
+ # ary = [1, nil]
70
+ # # Captured variable:
71
+ # # {
72
+ # # name: "ary",
73
+ # # type: "Array",
74
+ # # members: [
75
+ # # { name: "[0]", value: "1", type: "Integer" },
76
+ # # { name: "[1]", value: "nil", type: "NilClass" }
77
+ # # ]
78
+ # # }
79
+ # ```
80
+ #
81
+ class Variable
82
+ ##
83
+ # Max depth to convert on compound variables
84
+ MAX_DEPTH = 3
85
+
86
+ ##
87
+ # Max number of member variables to evaluate in compound variables
88
+ MAX_MEMBERS = 1000
89
+
90
+ ##
91
+ # Max length on variable inspect results. Truncate extra and replace
92
+ # with ellipsis.
93
+ MAX_STRING_LENGTH = 500
94
+
95
+ ##
96
+ # @private Minimum amount of size limit needed for evaluation.
97
+ MIN_REQUIRED_SIZE = 100
98
+
99
+ ##
100
+ # @private Message to display on variables when snapshot buffer is
101
+ # full.
102
+ BUFFER_FULL_MSG =
103
+ "Buffer full. Use an expression to see more data.".freeze
104
+
105
+ ##
106
+ # @private Error message when variable can't be converted.
107
+ FAIL_CONVERSION_MSG = "Error: Unable to inspect value".freeze
108
+
109
+ ##
110
+ # @private Name of the variable, if any.
111
+ # @return [String]
112
+ attr_accessor :name
113
+
114
+ ##
115
+ # @private Simple value of the variable.
116
+ # @return [String]
117
+ attr_accessor :value
118
+
119
+ ##
120
+ # @private Variable type (e.g. MyClass). If the variable is split with
121
+ # var_table_index, type goes next to value.
122
+ # @return [String]
123
+ attr_accessor :type
124
+
125
+ ##
126
+ # @private Members contained or pointed to by the variable.
127
+ # @return [Array<Variable>]
128
+ attr_accessor :members
129
+
130
+ ##
131
+ # @private Reference to a variable in the shared variable table. More
132
+ # than one variable can reference the same variable in the table. The
133
+ # var_table_index field is an index into variable_table in Breakpoint.
134
+ attr_accessor :var_table_index
135
+
136
+ ##
137
+ # @private The variable table this variable references to (if
138
+ # var_table_index is set).
139
+ attr_accessor :var_table
140
+
141
+ ##
142
+ # @private Status associated with the variable. This field will
143
+ # usually stay unset. A status of a single variable only applies to
144
+ # that variable or expression. The rest of breakpoint data still
145
+ # remains valid. Variables might be reported in error state even when
146
+ # breakpoint is not in final state.
147
+ # The message may refer to variable name with refers_to set to
148
+ # VARIABLE_NAME. Alternatively refers_to will be set to
149
+ # VARIABLE_VALUE. In either case variable value and members will be
150
+ # unset.
151
+ attr_accessor :status
152
+
153
+ ##
154
+ # @private The original Ruby object this Breakpoint::Variable is based
155
+ # upon.
156
+ attr_accessor :source_var
157
+
158
+ ##
159
+ # @private Create an empty Variable object.
160
+ def initialize
161
+ @members = []
162
+ end
163
+
164
+ ##
165
+ # Convert a Ruby variable into a
166
+ # Google::Cloud::Debugger::Breakpoint::Variable object. If
167
+ # a variable table is provided, it will store all the subsequently
168
+ # created compound variables into the variable table for sharing.
169
+ #
170
+ # @param [Any] source Source Ruby variable to convert from
171
+ # @param [String] name Name of the varaible
172
+ # @param [Integer] depth Number of levels to evaluate in compound
173
+ # variables. Default to
174
+ # {Google::Cloud::Debugger::Breakpoint::Variable::MAX_DEPTH}
175
+ # @param [Breakpoint::VariableTable] var_table A variable table
176
+ # to store shared compound variables. Optional.
177
+ # @param [Integer] limit Maximum number of bytes this conversion
178
+ # should take. This include nested compound member variables'
179
+ # conversions.
180
+ #
181
+ # @example Simple variable conversion
182
+ # x = 3.0
183
+ # var = Google::Cloud::Debugger::Breakpoint::Variable.from_rb_var \
184
+ # x, name: "x"
185
+ # var.name #=> "x"
186
+ # var.value #=> "3.0"
187
+ # var.type #=> "Float"
188
+ #
189
+ # @example Hash conversion
190
+ # hash = {a: 1.0, b: :two}
191
+ # var = Google::Cloud::Debugger::Breakpoint::Variable.from_rb_var \
192
+ # hash, name: "hash"
193
+ # var.name #=> "hash"
194
+ # var.type #=> "Hash"
195
+ # var.members[0].name #=> "a"
196
+ # var.members[0].value #=> "1.0"
197
+ # var.members[0].type #=> "Float"
198
+ # var.members[1].name #=> "b"
199
+ # var.members[1].value #=> ":two"
200
+ # var.members[1].type #=> "Symbol"
201
+ #
202
+ # @example Custom compound variable conversion
203
+ # foo = Foo.new(a: 1.0, b: [])
204
+ # foo.inspect #=> "#<Foo:0xXXXXXX @a=1.0, @b=[]>"
205
+ # var = Google::Cloud::Debugger::Breakpoint::Variable.from_rb_var \
206
+ # foo, name: "foo"
207
+ # var.name #=> "foo"
208
+ # var.type #=> "Foo"
209
+ # var.members[0].name #=> "@a"
210
+ # var.members[0].value #=> "1.0"
211
+ # var.members[0].type #=> "Float"
212
+ # var.members[1].name #=> "@b"
213
+ # var.members[1].value #=> "[]"
214
+ # var.members[1].type #=> "Array"
215
+ #
216
+ # @example Use variable table for shared compound variables
217
+ # hash = {a: 1.0}
218
+ # ary = [hash, hash]
219
+ # var_table = Google::Cloud::Debugger::Breakpoint::VariableTable.new
220
+ # var = Google::Cloud::Debugger::Breakpoint::Variable.from_rb_var \
221
+ # ary, name: "ary", var_table: var_table
222
+ # var.name #=> "ary"
223
+ # var.var_table_index #=> 0
224
+ # var_table[0].type #=> "Array"
225
+ # var_table[0].members[0].name #=> "[0]"
226
+ # var_table[0].members[0].var_table_index #=> 1
227
+ # var_table[0].members[1].name #=> "[1]"
228
+ # var_table[0].members[1].var_table_index #=> 1
229
+ # var_table[1].type #=> "Hash"
230
+ # var_table[1].members[0].name #=> "a"
231
+ # var_table[1].members[0].type #=> "Float"
232
+ # var_table[1].members[0].value #=> "1.0"
233
+ #
234
+ # @return [Google::Cloud::Debugger::Breakpoint::Variable] Converted
235
+ # variable.
236
+ #
237
+ def self.from_rb_var source, name: nil, depth: MAX_DEPTH,
238
+ var_table: nil, limit: nil
239
+ return source if source.is_a? Variable
240
+
241
+ if limit && limit < MIN_REQUIRED_SIZE
242
+ return buffer_full_variable var_table
243
+ end
244
+
245
+ # If source is a non-empty Array or Hash, or source has instance
246
+ # variables, evaluate source as a compound variable.
247
+ if compound_var?(source) && depth > 0
248
+ from_compound_var source, name: name, depth: depth,
249
+ var_table: var_table, limit: limit
250
+ else
251
+ from_primitive_var source, name: name, limit: limit
252
+ end
253
+ rescue StandardError
254
+ new.tap do |var|
255
+ var.name = name.to_s if name
256
+ var.set_error_state FAIL_CONVERSION_MSG
257
+ var.source_var = source
258
+ end
259
+ end
260
+
261
+ ##
262
+ # @private Helper method that converts primitive variables.
263
+ def self.from_primitive_var source, name: nil, limit: nil
264
+ new.tap do |var|
265
+ var.name = name.to_s if name
266
+ var.type = source.class.to_s
267
+ var.source_var = source
268
+ limit = deduct_limit limit,
269
+ var.name.to_s.bytesize + var.type.bytesize
270
+
271
+ var.value = truncate_value source.inspect, limit
272
+ end
273
+ end
274
+
275
+ ##
276
+ # @private Helper method that converts compound variables.
277
+ def self.from_compound_var source, name: nil, depth: MAX_DEPTH,
278
+ var_table: nil, limit: nil
279
+ return source if source.is_a? Variable
280
+
281
+ if limit && limit < MIN_REQUIRED_SIZE
282
+ return buffer_full_variable var_table
283
+ end
284
+
285
+ var = new
286
+ var.name = name.to_s if name
287
+ var.source_var = source
288
+ limit = deduct_limit limit, var.name.to_s.bytesize
289
+
290
+ if var_table
291
+ var.var_table = var_table
292
+ var.var_table_index =
293
+ var_table.rb_var_index(source) ||
294
+ add_shared_compound_var(source, depth, var_table, limit: limit)
295
+ else
296
+ var.type = source.class.to_s
297
+ limit = deduct_limit limit, var.type.bytesize
298
+ add_compound_members var, source, depth, limit: limit
299
+ end
300
+ var
301
+ end
302
+
303
+ ##
304
+ # @private Determine if a given Ruby variable is a compound variable.
305
+ def self.compound_var? source
306
+ ((source.is_a?(Hash) || source.is_a?(Array)) && !source.empty?) ||
307
+ !source.instance_variables.empty?
308
+ end
309
+
310
+ ##
311
+ # @private Add a shared compound variable to the breakpoint
312
+ # variable table.
313
+ def self.add_shared_compound_var source, depth, var_table, limit: nil
314
+ var = new
315
+ var.type = source.class.to_s
316
+ var.source_var = source
317
+ limit = deduct_limit limit, var.type.bytesize
318
+
319
+ table_index = var_table.size
320
+ var_table.add var
321
+
322
+ add_compound_members var, source, depth, var_table, limit: limit
323
+
324
+ table_index
325
+ end
326
+
327
+ ##
328
+ # @private Add member variables to a compound variable.
329
+ def self.add_compound_members var, source, depth, var_table = nil,
330
+ limit: nil
331
+ case source
332
+ when Hash
333
+ add_member_vars var, source, limit: limit do |(k, v), _, lmt|
334
+ from_rb_var v, name: k, depth: depth - 1, var_table: var_table,
335
+ limit: lmt
336
+ end
337
+ when Array
338
+ add_member_vars var, source, limit: limit do |el, i, lmt|
339
+ from_rb_var el, name: "[#{i}]", depth: depth - 1,
340
+ var_table: var_table, limit: lmt
341
+ end
342
+ else
343
+ members = source.instance_variables
344
+ add_member_vars var, members, limit: limit do |var_name, _, lmt|
345
+ instance_var = source.instance_variable_get var_name
346
+ from_rb_var instance_var, name: var_name, depth: depth - 1,
347
+ var_table: var_table, limit: lmt
348
+ end
349
+ end
350
+ end
351
+
352
+ ##
353
+ # @private Help interate through collection of member variables for
354
+ # compound variables.
355
+ def self.add_member_vars var, members, limit: nil
356
+ members.each_with_index do |member, i|
357
+ member_var = yield member, i, limit
358
+
359
+ limit = deduct_limit limit, member_var.total_size
360
+
361
+ buffer_full = (limit && limit < 0) ||
362
+ i >= MAX_MEMBERS ||
363
+ member_var.buffer_full_variable?
364
+
365
+ if buffer_full
366
+ var.members << Variable.new.tap do |last_var|
367
+ last_var.set_error_state \
368
+ "Only first #{i} items were captured. Use in " \
369
+ "an expression to see all items."
370
+ end
371
+ break
372
+ else
373
+ var.members << member_var
374
+ end
375
+ end
376
+ end
377
+
378
+ ##
379
+ # @private Create an empty variable that points to the shared
380
+ # "Buffer Full" variable in the given variable table (always index 0)
381
+ # if a variable table is passed in. Otherwise create an error variable
382
+ # with the buffer full message.
383
+ def self.buffer_full_variable var_table = nil, name: nil
384
+ new.tap do |var|
385
+ var.name = name if name
386
+
387
+ if var_table && var_table.first &&
388
+ var_table.first.buffer_full_variable?
389
+ var.var_table = var_table
390
+ var.var_table_index = 0
391
+ else
392
+ var.set_error_state BUFFER_FULL_MSG
393
+ end
394
+ end
395
+ end
396
+
397
+ ##
398
+ # @private Helper method to calculate bytesize limit deduction.
399
+ def self.deduct_limit limit, used
400
+ limit.nil? ? nil : limit - used
401
+ end
402
+
403
+ private_class_method :add_compound_members,
404
+ :add_shared_compound_var,
405
+ :add_member_vars,
406
+ :compound_var?,
407
+ :deduct_limit
408
+
409
+ ##
410
+ # @private New Google::Cloud::Debugger::Breakpoint::Variable
411
+ # from a Google::Cloud::Debugger::V2::Variable object.
412
+ def self.from_grpc grpc
413
+ return new if grpc.nil?
414
+ new.tap do |o|
415
+ o.name = grpc.name
416
+ o.value = grpc.value
417
+ o.type = grpc.type
418
+ o.members = from_grpc_list grpc.members
419
+ o.var_table_index = var_table_index_from_grpc grpc.var_table_index
420
+ o.status = Breakpoint::StatusMessage.from_grpc grpc.status
421
+ end
422
+ end
423
+
424
+ ##
425
+ # @private New array of Google::Cloud::Debugger::Breakpoint::Variable
426
+ # from an array of Google::Cloud::Debugger::V2::Variable
427
+ # objects.
428
+ def self.from_grpc_list grpc_list
429
+ return [] if grpc_list.nil?
430
+ grpc_list.map { |var_grpc| from_grpc var_grpc }
431
+ end
432
+
433
+ ##
434
+ # @private Extract var_table_index from the equivalent GRPC struct
435
+ def self.var_table_index_from_grpc grpc
436
+ grpc.nil? ? nil : grpc.value
437
+ end
438
+
439
+ ##
440
+ # @private Limit string to MAX_STRING_LENTH. Replace extra characters
441
+ # with ellipsis
442
+ def self.truncate_value str, limit = nil
443
+ limit ||= MAX_STRING_LENGTH
444
+ str.gsub(/(.{#{limit - 3}}).+/, '\1...')
445
+ end
446
+ private_class_method :add_compound_members, :truncate_value
447
+
448
+ ##
449
+ # @private Determines if the Variable has any data.
450
+ def empty?
451
+ name.nil? &&
452
+ value.nil? &&
453
+ type.nil? &&
454
+ members.nil? &&
455
+ var_table_index.nil? &&
456
+ status.nil?
457
+ end
458
+
459
+ ##
460
+ # Exports the Variable to a
461
+ # Google::Cloud::Debugger::V2::Variable object.
462
+ def to_grpc
463
+ return nil if empty?
464
+ Google::Cloud::Debugger::V2::Variable.new(
465
+ name: name.to_s,
466
+ value: value.to_s,
467
+ type: type.to_s,
468
+ var_table_index: var_table_index_to_grpc,
469
+ members: members_to_grpc || [],
470
+ status: status_to_grpc
471
+ )
472
+ end
473
+
474
+ ##
475
+ # Set this variable to an error state by setting the status field
476
+ def set_error_state message, refers_to: StatusMessage::VARIABLE_VALUE
477
+ @status = StatusMessage.new.tap do |s|
478
+ s.is_error = true
479
+ s.refers_to = refers_to
480
+ s.description = message
481
+ end
482
+ end
483
+
484
+ ##
485
+ # Calculate the total bytesize of all the attributes and that of the
486
+ # member variables, plus references into other variables in the
487
+ # variable table.
488
+ #
489
+ # @return [Integer] The total payload size of this variable in bytes.
490
+ def total_size
491
+ unless @total_size
492
+ vars = [self, *(unique_members || [])]
493
+
494
+ @total_size = vars.inject payload_size do |sum, var|
495
+ if var.var_table && var.var_table_index
496
+ sum + var.var_table[var.var_table_index].total_size
497
+ else
498
+ sum
499
+ end
500
+ end
501
+ end
502
+
503
+ @total_size
504
+ end
505
+
506
+ ##
507
+ # Calculate the bytesize of all the attributes and that of the
508
+ # member variables.
509
+ #
510
+ # @return [Integer] The total payload size of this variable in bytes.
511
+ def payload_size
512
+ unless @payload_size
513
+ @payload_size = name.to_s.bytesize +
514
+ type.to_s.bytesize +
515
+ value.to_s.bytesize
516
+
517
+ unless members.nil?
518
+ @payload_size = members.inject @payload_size do |sum, member|
519
+ sum + member.payload_size
520
+ end
521
+ end
522
+ end
523
+
524
+ @payload_size
525
+ end
526
+
527
+ ##
528
+ # @private Get a unique array of members that don't reference
529
+ # same object in variable table
530
+ def unique_members
531
+ seen_indices = {}
532
+
533
+ members.select do |member|
534
+ if seen_indices[member.var_table_index]
535
+ false
536
+ else
537
+ seen_indices[member.var_table_index] = true
538
+ true
539
+ end
540
+ end
541
+ end
542
+
543
+ ##
544
+ # @private Whether this variable is a reference variable into
545
+ # the shared variable table or not.
546
+ def reference_variable?
547
+ value.nil? && members.empty? && !var_table_index.nil?
548
+ end
549
+
550
+ ##
551
+ # @private Check if a given variable is a buffer full variable, or an
552
+ # reference variable to the shared buffer full variable
553
+ def buffer_full_variable?
554
+ if (status &&
555
+ status.description == BUFFER_FULL_MSG) ||
556
+ (var_table &&
557
+ reference_variable? &&
558
+ var_table_index.zero? &&
559
+ var_table[0] &&
560
+ var_table[0].status &&
561
+ var_table[0].status.description == BUFFER_FULL_MSG)
562
+ true
563
+ else
564
+ false
565
+ end
566
+ end
567
+
568
+ private
569
+
570
+ ##
571
+ # @private Exports the Variable status to grpc
572
+ def status_to_grpc
573
+ status.nil? ? nil : status.to_grpc
574
+ end
575
+
576
+ ##
577
+ # @private Exports the Variable var_table_index attribute to
578
+ # an Int32Value gRPC struct
579
+ def var_table_index_to_grpc
580
+ return unless var_table_index
581
+
582
+ Google::Protobuf::Int32Value.new value: var_table_index
583
+ end
584
+
585
+ ##
586
+ # @private Exports the Variable members to an array of
587
+ # Google::Cloud::Debugger::V2::Variable objects.
588
+ def members_to_grpc
589
+ members.nil? ? nil : members.map(&:to_grpc)
590
+ end
591
+ end
592
+ end
593
+ end
594
+ end
595
+ end