contrast-agent 4.10.0 → 4.13.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (87) hide show
  1. checksums.yaml +4 -4
  2. data/ext/cs__assess_module/cs__assess_module.c +48 -0
  3. data/ext/cs__assess_module/cs__assess_module.h +7 -0
  4. data/ext/cs__common/cs__common.c +24 -7
  5. data/ext/cs__common/cs__common.h +12 -2
  6. data/ext/cs__contrast_patch/cs__contrast_patch.c +48 -11
  7. data/ext/cs__contrast_patch/cs__contrast_patch.h +5 -2
  8. data/ext/cs__os_information/cs__os_information.c +31 -0
  9. data/ext/cs__os_information/cs__os_information.h +7 -0
  10. data/ext/{cs__protect_kernel → cs__os_information}/extconf.rb +0 -0
  11. data/lib/contrast/agent/assess/contrast_event.rb +1 -1
  12. data/lib/contrast/agent/assess/contrast_object.rb +1 -4
  13. data/lib/contrast/agent/assess/policy/dynamic_source_factory.rb +2 -0
  14. data/lib/contrast/agent/assess/policy/preshift.rb +25 -11
  15. data/lib/contrast/agent/assess/policy/propagation_method.rb +2 -116
  16. data/lib/contrast/agent/assess/policy/propagation_node.rb +4 -4
  17. data/lib/contrast/agent/assess/policy/propagator/database_write.rb +2 -0
  18. data/lib/contrast/agent/assess/policy/propagator/match_data.rb +4 -4
  19. data/lib/contrast/agent/assess/policy/propagator/remove.rb +4 -9
  20. data/lib/contrast/agent/assess/policy/source_method.rb +2 -71
  21. data/lib/contrast/agent/assess/policy/trigger_method.rb +4 -107
  22. data/lib/contrast/agent/assess/policy/trigger_node.rb +52 -19
  23. data/lib/contrast/agent/assess/property/tagged.rb +15 -132
  24. data/lib/contrast/agent/deadzone/policy/policy.rb +6 -0
  25. data/lib/contrast/agent/inventory/dependency_usage_analysis.rb +2 -1
  26. data/lib/contrast/agent/metric_telemetry_event.rb +26 -0
  27. data/lib/contrast/agent/middleware.rb +22 -0
  28. data/lib/contrast/agent/patching/policy/after_load_patcher.rb +0 -1
  29. data/lib/contrast/agent/patching/policy/method_policy.rb +54 -9
  30. data/lib/contrast/agent/patching/policy/patch.rb +37 -238
  31. data/lib/contrast/agent/patching/policy/patcher.rb +3 -42
  32. data/lib/contrast/agent/request.rb +5 -3
  33. data/lib/contrast/agent/request_context.rb +32 -11
  34. data/lib/contrast/agent/request_handler.rb +7 -3
  35. data/lib/contrast/agent/rule_set.rb +2 -4
  36. data/lib/contrast/agent/scope.rb +32 -20
  37. data/lib/contrast/agent/startup_metrics_telemetry_event.rb +71 -0
  38. data/lib/contrast/agent/static_analysis.rb +4 -2
  39. data/lib/contrast/agent/telemetry.rb +129 -0
  40. data/lib/contrast/agent/telemetry_event.rb +34 -0
  41. data/lib/contrast/agent/thread_watcher.rb +43 -14
  42. data/lib/contrast/agent/tracepoint_hook.rb +11 -3
  43. data/lib/contrast/agent/version.rb +1 -1
  44. data/lib/contrast/agent.rb +6 -1
  45. data/lib/contrast/components/api.rb +34 -0
  46. data/lib/contrast/components/app_context.rb +24 -0
  47. data/lib/contrast/components/assess.rb +7 -0
  48. data/lib/contrast/components/config.rb +90 -11
  49. data/lib/contrast/components/contrast_service.rb +6 -0
  50. data/lib/contrast/config/api_configuration.rb +22 -0
  51. data/lib/contrast/config/assess_configuration.rb +1 -0
  52. data/lib/contrast/config/env_variables.rb +25 -0
  53. data/lib/contrast/config/root_configuration.rb +1 -0
  54. data/lib/contrast/config/service_configuration.rb +2 -1
  55. data/lib/contrast/config.rb +1 -0
  56. data/lib/contrast/configuration.rb +3 -0
  57. data/lib/contrast/framework/manager.rb +14 -12
  58. data/lib/contrast/framework/rails/patch/action_controller_live_buffer.rb +9 -6
  59. data/lib/contrast/framework/rails/patch/support.rb +31 -29
  60. data/lib/contrast/logger/application.rb +4 -0
  61. data/lib/contrast/utils/assess/propagation_method_utils.rb +129 -0
  62. data/lib/contrast/utils/assess/property/tagged_utils.rb +142 -0
  63. data/lib/contrast/utils/assess/source_method_utils.rb +83 -0
  64. data/lib/contrast/utils/assess/trigger_method_utils.rb +138 -0
  65. data/lib/contrast/utils/class_util.rb +58 -44
  66. data/lib/contrast/utils/exclude_key.rb +20 -0
  67. data/lib/contrast/utils/io_util.rb +42 -34
  68. data/lib/contrast/utils/lru_cache.rb +45 -0
  69. data/lib/contrast/utils/metrics_hash.rb +59 -0
  70. data/lib/contrast/utils/os.rb +23 -0
  71. data/lib/contrast/utils/patching/policy/patch_utils.rb +232 -0
  72. data/lib/contrast/utils/patching/policy/patcher_utils.rb +54 -0
  73. data/lib/contrast/utils/requests_client.rb +150 -0
  74. data/lib/contrast/utils/ruby_ast_rewriter.rb +1 -1
  75. data/lib/contrast/utils/telemetry.rb +77 -0
  76. data/lib/contrast/utils/telemetry_identifier.rb +137 -0
  77. data/lib/contrast.rb +19 -1
  78. data/resources/assess/policy.json +12 -6
  79. data/resources/deadzone/policy.json +86 -5
  80. data/ruby-agent.gemspec +2 -1
  81. data/service_executables/VERSION +1 -1
  82. data/service_executables/linux/contrast-service +0 -0
  83. data/service_executables/mac/contrast-service +0 -0
  84. metadata +32 -14
  85. data/ext/cs__protect_kernel/cs__protect_kernel.c +0 -47
  86. data/ext/cs__protect_kernel/cs__protect_kernel.h +0 -12
  87. data/lib/contrast/extension/protect/kernel.rb +0 -29
@@ -15,7 +15,7 @@ module Contrast
15
15
  case ret
16
16
  when Array
17
17
  idx = 0
18
- while idx < ret.size
18
+ while idx < ret.length
19
19
  return_value = ret[idx]
20
20
  index = idx
21
21
  idx += 1
@@ -34,7 +34,7 @@ module Contrast
34
34
 
35
35
  def captures_tagger propagation_node, preshift, ret, _block
36
36
  idx = 0
37
- while idx < ret.size
37
+ while idx < ret.length
38
38
  return_value = ret[idx]
39
39
  index = idx
40
40
  idx += 1
@@ -48,7 +48,7 @@ module Contrast
48
48
 
49
49
  def to_a_tagger propagation_node, preshift, ret, _block
50
50
  idx = 0
51
- while idx < ret.size
51
+ while idx < ret.length
52
52
  return_value = ret[idx]
53
53
  index = idx
54
54
  idx += 1
@@ -61,7 +61,7 @@ module Contrast
61
61
 
62
62
  def values_at_tagger propagation_node, preshift, ret, _block
63
63
  idx = 0
64
- while idx < ret.size
64
+ while idx < ret.length
65
65
  return_value = ret[idx]
66
66
  return_index = idx
67
67
  idx += 1
@@ -26,7 +26,6 @@ module Contrast
26
26
  return unless (properties = Contrast::Agent::Assess::Tracker.properties!(target))
27
27
 
28
28
  source_string = source.is_a?(String) ? source : source.to_s
29
-
30
29
  # If the lengths are the same, we should just copy the tags because nothing was removed, but a new
31
30
  # instance could have been created. copy_from will handle the case where the source is the target.
32
31
  if source_string.length == target.length
@@ -34,10 +33,7 @@ module Contrast
34
33
  return
35
34
  end
36
35
 
37
- source_chars = source_string.chars
38
36
  source_idx = 0
39
-
40
- target_chars = target.chars
41
37
  target_idx = 0
42
38
 
43
39
  remove_ranges = []
@@ -45,10 +41,9 @@ module Contrast
45
41
 
46
42
  # loop over the target, the result of the delete every range of characters that it differs from the
47
43
  # source represents a section that was deleted. these sections need to have their tags updated
48
- target_len = target_chars.length
49
- while target_idx < target_len
50
- target_char = target_chars[target_idx]
51
- source_char = source_chars[source_idx]
44
+ while target_idx < target.length
45
+ target_char = target[target_idx]
46
+ source_char = source_string[source_idx]
52
47
  if target_char == source_char
53
48
  target_idx += 1
54
49
  if start
@@ -63,7 +58,7 @@ module Contrast
63
58
 
64
59
  # once we're done looping over the target, anything left over is extra from the source that was
65
60
  # deleted. tags applying to it need to be removed.
66
- remove_ranges << (source_idx...source_chars.length) if source_idx != source_chars.length
61
+ remove_ranges << (source_idx...source_string.length) if source_idx != source_string.length
67
62
 
68
63
  # handle deleting the removed ranges
69
64
  properties.delete_tags_at_ranges(remove_ranges)
@@ -6,6 +6,7 @@ require 'contrast/agent/assess/policy/source_validation/source_validation'
6
6
  require 'contrast/components/logger'
7
7
  require 'contrast/utils/object_share'
8
8
  require 'contrast/utils/sha256_builder'
9
+ require 'contrast/utils/assess/source_method_utils'
9
10
 
10
11
  module Contrast
11
12
  module Agent
@@ -16,6 +17,7 @@ module Contrast
16
17
  # used in Assess vulnerability detection.
17
18
  module SourceMethod
18
19
  extend Contrast::Components::Logger::InstanceMethods
20
+ extend Contrast::Utils::Assess::SourceMethodUtils
19
21
 
20
22
  PARAMETER_TYPE = 'PARAMETER'
21
23
  PARAMETER_KEY_TYPE = 'PARAMETER_KEY'
@@ -133,15 +135,6 @@ module Contrast
133
135
  !Contrast::Agent::Assess::Tracker.trackable?(key)
134
136
  end
135
137
 
136
- # Safely duplicate the target, or return nil
137
- #
138
- # @param target [Object] the thing to check for duplication
139
- def safe_dup target
140
- target.dup
141
- rescue StandardError => _e
142
- nil
143
- end
144
-
145
138
  # Hash is designed to keep one instance of the string key in it. We need to remove the existing one and
146
139
  # replace it with our new tracked one.
147
140
  def handle_hash_key target, to_replace
@@ -174,68 +167,6 @@ module Contrast
174
167
  properties.build_event(source_node, target, object, ret, args, source_type, source_name)
175
168
  end
176
169
 
177
- # Find the name of the source
178
- #
179
- # @param source_node [Contrast::Agent::Assess::Policy::SourceNode] the node to direct applying this source
180
- # event
181
- # @param object [Object] the Object on which the method was invoked
182
- # @param ret [Object] the Return of the invoked method
183
- # @param args [Array<Object>] the Arguments with which the method was invoked
184
- # @return [String, nil] the human readable name of the target to which this source event applies, or nil if
185
- # none provided by the node
186
- def determine_source_name source_node, object, ret, *args
187
- return source_node.get_property('dynamic_source_name') if source_node.type == 'UNTRUSTED_DATABASE'
188
-
189
- source_node_source = source_node.sources[0]
190
- case source_node_source
191
- when nil
192
- nil
193
- when Contrast::Utils::ObjectShare::RETURN_KEY
194
- ret
195
- when Contrast::Utils::ObjectShare::OBJECT_KEY
196
- object
197
- else
198
- args[source_node_source]
199
- end
200
- end
201
-
202
- # Determine if we should analyze this method invocation for a Source or not. We should if we have enough
203
- # information to build the context of this invocation, we're not disabled, and we can't immediately
204
- # determine the invocation was done safely.
205
- #
206
- # @param method_policy [Contrast::Agent::Patching::Policy::MethodPolicy] the policy that applies to the
207
- # method being called
208
- # @param object [Object] the Object on which the method was invoked
209
- # @param ret [Object] the Return of the invoked method
210
- # @param args [Array<Object>] the Arguments with which the method was invoked
211
- # @return [boolean] if the invocation of this method should be analyzed
212
- def analyze? method_policy, object, ret, args
213
- return false unless method_policy&.source_node
214
- return false unless ::Contrast::ASSESS.enabled?
215
- return false unless Contrast::Agent::REQUEST_TRACKER.current&.analyze_request?
216
-
217
- !safe_invocation?(method_policy.source_node, object, ret, args)
218
- end
219
-
220
- # Determine if the method was invoked safely.
221
- #
222
- # @param source_node [Contrast::Agent::Assess::Policy::SourceNode] the node to direct applying this source
223
- # event
224
- # @param _object [Object] the Object on which the method was invoked
225
- # @param _ret [Object] the Return of the invoked method
226
- # @param args [Array<Object>] the Arguments with which the method was invoked
227
- # @return [boolean] if the invocation of this method was safe
228
- def safe_invocation? source_node, _object, _ret, args
229
- # According the the Rack Specification https://github.com/rack/rack/blob/master/SPEC.rdoc, any header
230
- # from the Request will start with HTTP_. As such, only Headers with that key should be considered for
231
- # tracking, as the others have come from the Framework or Middleware stashing in the ENV. Rails, for
232
- # instance, uses action_dispatch. to store several values. Technically, you can't call
233
- # Rack::Request#get_header without a parameter, and that parameter should be a String, but trust no one.
234
- source_node.id == 'Assess:Source:Rack::Request::Env#get_header' &&
235
- args&.any? &&
236
- !args[0].to_s.start_with?('HTTP_')
237
- end
238
-
239
170
  # Find the literal target of the propagation
240
171
  #
241
172
  # @param source_node [Contrast::Agent::Assess::Policy::SourceNode] the node to direct applying this source
@@ -6,6 +6,7 @@ require 'contrast/agent/assess/policy/trigger_validation/trigger_validation'
6
6
  require 'contrast/components/logger'
7
7
  require 'contrast/utils/object_share'
8
8
  require 'contrast/utils/sha256_builder'
9
+ require 'contrast/utils/assess/trigger_method_utils'
9
10
 
10
11
  module Contrast
11
12
  module Agent
@@ -17,6 +18,7 @@ module Contrast
17
18
  # report is issued to the Service.
18
19
  module TriggerMethod
19
20
  extend Contrast::Components::Logger::InstanceMethods
21
+ extend Contrast::Utils::Assess::TriggerMethodUtils
20
22
 
21
23
  # The level of TeamServer compliance our traces meet when in the abnormal condition of being dataflow rules
22
24
  # without routes.
@@ -84,6 +86,8 @@ module Contrast
84
86
  # activity message does not exist, b/c we're invoked outside of a request context, build an activity and
85
87
  # immediately report it with the finding.
86
88
  #
89
+ # TODO: RUBY-1351
90
+ #
87
91
  # @param finding [Contrast::Api::Dtm::Finding] the Finding to report.
88
92
  # @param request [Contrast::Agent::Request] our wrapper around the Rack::Request.
89
93
  def report_finding finding, request = nil
@@ -108,64 +112,10 @@ module Contrast
108
112
 
109
113
  private
110
114
 
111
- # A request is reportable if it is not from ActionController::Live
112
- #
113
- # @param env [Hash] the env of the Request
114
- # @return [Boolean]
115
- def reportable? env
116
- !(defined?(ActionController::Live) &&
117
- env &&
118
- env['action_controller.instance'].cs__class.included_modules.include?(ActionController::Live))
119
- end
120
-
121
- # Find the request for this finding. This assumes, for now, that if there is an active request, then that
122
- # is the request to report. Otherwise, we'll use the first request found in the events of the
123
- # source_object.
124
- #
125
- # @param source [Object,nil] some Object used as the source of a trigger event
126
- # @return [Contrast::Agent::Request,nil] the request from which the dataflow on the request originated.
127
- def find_request source
128
- return Contrast::Agent::REQUEST_TRACKER.current.request if Contrast::Agent::REQUEST_TRACKER.current
129
- return unless (properties = Contrast::Agent::Assess::Tracker.properties(source))
130
-
131
- properties.events.each do |event|
132
- next unless event.cs__is_a?(Contrast::Agent::Assess::Events::SourceEvent)
133
-
134
- return event.request if event.request
135
- end
136
- nil
137
- end
138
-
139
115
  def settings
140
116
  Contrast::Agent::FeatureState.instance
141
117
  end
142
118
 
143
- # This is our method that actually checks the taint on the object our trigger_node targets.
144
- #
145
- # @param trigger_node [Contrast::Agent::Assess::Policy::TriggerNode] the node to direct applying this
146
- # trigger event
147
- # @param source [Object] the source of the Trigger Event
148
- # @param object [Object] the Object on which the method was invoked
149
- # @param ret [Object] the Return of the invoked method
150
- # @param args [Array<Object>] the Arguments with which the method was invoked
151
- def apply_trigger trigger_node, source, object, ret, *args
152
- return unless trigger_node
153
- return if trigger_node.rule_disabled?
154
- return if trigger_node.dataflow? && source.nil?
155
-
156
- if trigger_node.regexp_rule?
157
- apply_regexp_rule(trigger_node, source, object, ret, *args)
158
- elsif trigger_node.custom_trigger?
159
- trigger_node.apply_custom_trigger(trigger_node, source, object, ret, *args)
160
- elsif trigger_node.dataflow?
161
- apply_dataflow_rule(trigger_node, source, object, ret, *args)
162
- else # trigger rule - just calling the method is dangerous
163
- build_finding(trigger_node, source, object, ret, *args)
164
- end
165
- rescue StandardError => e
166
- logger.warn('Unable to apply trigger', e, node_id: trigger_node.id)
167
- end
168
-
169
119
  # Given the marker from the trigger_node (the pointer indicating the entity from which the taint
170
120
  # originated), return the entity on which this trigger needs to operate.
171
121
  #
@@ -199,59 +149,6 @@ module Contrast
199
149
  end
200
150
  end
201
151
 
202
- # This is our method that actually checks the taint on the object our trigger_node targets for our Regexp
203
- # based rules.
204
- #
205
- # @param trigger_node [Contrast::Agent::Assess::Policy::TriggerNode] the node to direct applying this
206
- # trigger event
207
- # @param source [Object] the source of the Trigger Event
208
- # @param object [Object] the Object on which the method was invoked
209
- # @param ret [Object] the Return of the invoked method
210
- # @param args [Array<Object>] the Arguments with which the method was invoked
211
- def apply_regexp_rule trigger_node, source, object, ret, *args
212
- return unless source.is_a?(String)
213
- return if trigger_node.good_value && source.match?(trigger_node.good_value)
214
- return if trigger_node.bad_value && source !~ trigger_node.bad_value
215
-
216
- build_finding(trigger_node, source, object, ret, *args)
217
- end
218
-
219
- # This is our method that actually checks the taint on the object our trigger_node targets for our Dataflow
220
- # based rules.
221
- #
222
- # @param trigger_node [Contrast::Agent::Assess::Policy::TriggerNode] the node to direct applying this
223
- # trigger event
224
- # @param source [Object] the source of the Trigger Event
225
- # @param object [Object] the Object on which the method was invoked
226
- # @param ret [Object] the Return of the invoked method
227
- # @param args [Array<Object>] the Arguments with which the method was invoked
228
- def apply_dataflow_rule trigger_node, source, object, ret, *args
229
- return unless source
230
-
231
- if Contrast::Agent::Assess::Tracker.trackable?(source)
232
- return unless Contrast::Agent::Assess::Tracker.tracked?(source)
233
- return unless trigger_node.violated?(source)
234
-
235
- build_finding(trigger_node, source, object, ret, *args)
236
- elsif Contrast::Utils::DuckUtils.iterable_hash?(source)
237
- source.each_pair do |key, value|
238
- apply_dataflow_rule(trigger_node, key, object, ret, *args)
239
- apply_dataflow_rule(trigger_node, value, object, ret, *args)
240
- end
241
- elsif Contrast::Utils::DuckUtils.iterable_enumerable?(source)
242
- source.each do |value|
243
- apply_dataflow_rule(trigger_node, value, object, ret, *args)
244
- end
245
- else
246
- logger.debug('Trigger source is untrackable. Unable to inspect.',
247
- node_id: trigger_node.id,
248
- source_id: source.__id__,
249
- source_type: source.cs__class.to_s,
250
- frozen: source.cs__frozen?)
251
- logger.trace(source.to_s[0..99])
252
- end
253
- end
254
-
255
152
  def append_events finding, trigger_node, source, object, ret, args
256
153
  append_from_source(finding, source)
257
154
  finding.events << Contrast::Agent::Assess::Events::EventFactory.build(trigger_node, source, object, ret,
@@ -14,7 +14,7 @@ module Contrast
14
14
  # specifically for those methods which result in the trigger of a
15
15
  # vulnerability (indicate points in the application where uncontrolled
16
16
  # user input can do damage).
17
- class TriggerNode < PolicyNode
17
+ class TriggerNode < PolicyNode # rubocop:disable Metrics/ClassLength
18
18
  JSON_BAD_VALUE = 'bad_value'
19
19
  JSON_GOOD_VALUE = 'good_value'
20
20
  JSON_DISALLOWED_TAGS = 'disallowed_tags'
@@ -104,8 +104,7 @@ module Contrast
104
104
 
105
105
  properties = Contrast::Agent::Assess::Tracker.properties(source)
106
106
  # find the ranges that violate the rule (untrusted, etc)
107
- vulnerable_ranges = ranges_with_all_tags(Contrast::Utils::StringUtils.ret_length(source), properties,
108
- required_tags)
107
+ vulnerable_ranges = ranges_with_all_tags(properties, required_tags)
109
108
  # if there aren't any vulnerable ranges, nope out
110
109
  return false if vulnerable_ranges.empty?
111
110
 
@@ -170,49 +169,56 @@ module Contrast
170
169
 
171
170
  tags.each do |tag|
172
171
  raise(ArgumentError, "Rule #{ rule_id } had an invalid tag. #{ tag } is not a known value.") unless
173
- Contrast::Api::Decorators::TraceTaintRangeTags::VALID_TAGS.include?(tag) ||
174
- Contrast::Api::Decorators::TraceTaintRangeTags::VALID_SOURCE_TAGS.include?(tag)
172
+ Contrast::Api::Decorators::TraceTaintRangeTags::VALID_TAGS.include?(tag) ||
173
+ Contrast::Api::Decorators::TraceTaintRangeTags::VALID_SOURCE_TAGS.include?(tag)
175
174
  end
176
175
  end
177
176
 
178
177
  # Find the ranges that satisfy all of the given tags.
179
178
  #
180
- # @param length [Integer] the length of the object which may have the
181
- # given tags -- used as the maximum index to search for all of the
182
- # tags.
183
179
  # @param properties [Contrast::Agent::Assess::Properties] the
184
180
  # properties to check for the tags
185
181
  # @param required_tags [Set<String>] the list of tags on which to match
186
182
  # @return [Array<Contrast::Agent::Assess::Tag>] the ranges satisfied
187
183
  # by the given conditions
188
- def ranges_with_all_tags length, properties, required_tags
184
+ def ranges_with_all_tags properties, required_tags
189
185
  return Contrast::Utils::ObjectShare::EMPTY_ARRAY unless matches_tags?(properties, required_tags)
190
186
 
191
- ranges = []
192
187
  chunking = false
188
+ ranges = []
189
+ # find the start and end range of required tags:
190
+ search_ranges = find_required_ranges properties, required_tags
191
+ start_range = search_ranges.first
192
+ end_range = search_ranges.last + 1
193
+
193
194
  # find all the indicies on the source that have all the given tags
194
- (0..length).each do |idx|
195
- tags_at = properties.tags_at(idx).to_a
195
+ while start_range < end_range
196
+
197
+ tags_at = properties.tags_at(start_range).to_a
196
198
  # only those that have all the required tags in the tags_at
197
199
  # satisfy the requirement
198
200
  satisfied = tags_at.any? && required_tags.all? { |tag| tags_at.any? { |found| found.label == tag } }
199
201
  # if this range matches all the required tags and we're already
200
202
  # chunking, meaning the previous range matched, do nothing
201
- next if satisfied && chunking
203
+ if satisfied && chunking
204
+ start_range += 1
205
+ next
206
+ end
202
207
 
203
208
  # if we are satisfied and we were not chunking, this represents
204
209
  # the start of the next range, so create a new entry.
205
210
  if satisfied
206
- ranges << Contrast::Agent::Assess::Tag.new('required', 0, idx)
211
+ ranges << Contrast::Agent::Assess::Tag.new('required', 0, start_range)
207
212
  chunking = true
208
- # if we are chunking and not satisfied, this represents the end
209
- # of the range, meaning the last index is what satisfied the
210
- # range. Because the range is exclusive end, we can just use this
211
- # index.
213
+ # if we are chunking and not satisfied, this represents the end
214
+ # of the range, meaning the last index is what satisfied the
215
+ # range. Because the range is exclusive end, we can just use this
216
+ # index.
212
217
  elsif chunking
213
- ranges[-1]&.update_end(idx)
218
+ ranges[-1]&.update_end(start_range)
214
219
  chunking = false
215
220
  end
221
+ start_range += 1
216
222
  end
217
223
  ranges
218
224
  end
@@ -265,6 +271,33 @@ module Contrast
265
271
 
266
272
  true
267
273
  end
274
+
275
+ # Range finder helper for #ranges_with_all_tags
276
+ #
277
+ # @param properties [Contrast::Agent::Assess::Properties] the properties to check for the tags
278
+ # @param required_tags [Set<String>] the list of tags on which to match
279
+ # @return [Array] of required tags ranges to search
280
+ def find_required_ranges properties, required_tags
281
+ start_range = 0
282
+ end_range = 0
283
+ required_tags_arr = required_tags.to_a
284
+ idx = 0
285
+
286
+ while idx < required_tags_arr.length
287
+ # find the start and end range of required tags:
288
+ start_temp = properties.fetch_tag(required_tags_arr[idx])[0].start_idx
289
+ end_temp = properties.fetch_tag(required_tags_arr[idx])[0].end_idx
290
+ # first iteration only
291
+ start_range = start_temp if idx.zero?
292
+ end_range = end_temp if idx.zero?
293
+
294
+ # find the tag with smallest ranges
295
+ start_range = start_temp if start_range < start_temp
296
+ end_range = end_temp if end_range > end_temp
297
+ idx += 1
298
+ end
299
+ [start_range, end_range]
300
+ end
268
301
  end
269
302
  end
270
303
  end
@@ -5,6 +5,7 @@ require 'contrast/agent/assess/tag'
5
5
  require 'contrast/utils/object_share'
6
6
  require 'contrast/utils/string_utils'
7
7
  require 'contrast/utils/tag_util'
8
+ require 'contrast/utils/assess/property/tagged_utils'
8
9
 
9
10
  module Contrast
10
11
  module Agent
@@ -13,6 +14,7 @@ module Contrast
13
14
  # This module serves to hold the functionality required for the
14
15
  # management of our dataflow tags.
15
16
  module Tagged
17
+ include Contrast::Utils::Assess::TaggedUtils
16
18
  # Is any tag present?
17
19
  # Creating Tags is expensive and we check for Tags all the time on
18
20
  # untracked things. ALWAYS!!! call this method before checking if an
@@ -45,125 +47,6 @@ module Contrast
45
47
  false
46
48
  end
47
49
 
48
- # Find all of the ranges that span a given index. This is used
49
- # in propagation when we need to shift tags about. For instance, in
50
- # the append method when we need to see if any tag at the end needs
51
- # to be expanded out to the size of the new String.
52
- #
53
- # Note: Tags do not know their key, so this is only the range covered
54
- #
55
- # @param idx [Integer] the index to check for tags
56
- # @return [Array<Contrast::Agent::Assess::Tag>] a set of all the Tags
57
- # covering the given index. This is only the ranges, not the names.
58
- def tags_at idx
59
- return Contrast::Utils::ObjectShare::EMPTY_ARRAY unless tracked?
60
-
61
- at = []
62
- tags.each_value do |tag_array|
63
- tag_array.each do |tag|
64
- if tag.covers?(idx)
65
- at << tag
66
- elsif tag.above?(idx)
67
- break
68
- end
69
- end
70
- end
71
- at
72
- end
73
-
74
- # given a range, select all tags in that range the selected tags are
75
- # shifted such that the start index of the new tag (0) aligns with
76
- # the given start index in the range
77
- #
78
- # current tags: 5-15
79
- # range : 5-10
80
- # result : 0-05
81
- #
82
- # Note that we disable Lint/DuplicateBranch in this branch in order
83
- # list out all tag range cases in the proper order to make this
84
- # easier to understand
85
- #
86
- # @param range [Range] the span to check, inclusive to exclusive
87
- # @return [Hash{String => Contrast::Agent::Assess::Tag}] the hash of
88
- # key to tags
89
- def tags_at_range range
90
- return Contrast::Utils::ObjectShare::EMPTY_HASH unless tracked?
91
-
92
- at = Hash.new { |h, k| h[k] = [] }
93
- tags.each_pair do |key, value|
94
- add = nil
95
- value.each do |tag|
96
- within_range = resize_to_range(tag, range)
97
- if within_range
98
- add ||= []
99
- add << within_range
100
- end
101
- end
102
- next unless add&.any?
103
-
104
- at[key] = add
105
- end
106
- at
107
- end
108
-
109
- # Given a tag name and range object, add a new tag to this
110
- # collection. If the given range touches an existing tag,
111
- # we'll combine the two, adjusting the existing one and
112
- # dropping this new one.
113
- #
114
- # @param label [String] the name of the tag
115
- # @param range [Range] the Range that the tag covers, inclusive to
116
- # exclusive
117
- def add_tag label, range
118
- length = range.end - range.begin
119
- tag = Contrast::Agent::Assess::Tag.new(label, length, range.begin)
120
- existing = fetch_tag(label)
121
- tags[label] = Contrast::Utils::TagUtil.ordered_merge(existing, tag)
122
- end
123
-
124
- def set_tags label, tag_ranges
125
- tags[label] = tag_ranges
126
- end
127
-
128
- # Remove all tags with a given label
129
- def delete_tags label
130
- tags.delete(label) if tracked?
131
- end
132
-
133
- # Reset the tag hash
134
- def clear_tags
135
- tags.clear if tracked?
136
- end
137
-
138
- # Returns a list of all current tag labels, most likely to be used for
139
- # a splat operation
140
- def tag_keys
141
- return Contrast::Utils::ObjectShare::EMPTY_ARRAY unless tracked?
142
-
143
- tags.keys
144
- end
145
-
146
- # Calls merge to combine touching or overlapping tags
147
- # Deletes empty tags
148
- def cleanup_tags
149
- return unless tracked?
150
-
151
- Contrast::Utils::TagUtil.merge_tags(tags)
152
- tags.delete_if { |_, value| value.empty? }
153
- end
154
-
155
- # We'll use this as a helper method to retrieve tags from the hash.
156
- # Because the hash auto-populates an empty array when we try to
157
- # access a tag in it, we cannot use the [] method without side
158
- # effect. To get around this, we'll use a fetch work around.
159
- #
160
- # @param label [Symbol] the label to look up
161
- # @return [Array<Contrast::Agent::Assess::Tag>] all the tags with
162
- # that label
163
- def fetch_tag label
164
- tags.fetch(label, nil) if tracked?
165
- end
166
-
167
50
  # Remove all tags within the given ranges.
168
51
  # This does not delete an entire tag if part of that tag is
169
52
  # outside this range, meaning we may reduce sizes of tags
@@ -218,19 +101,6 @@ module Contrast
218
101
  end
219
102
  end
220
103
 
221
- # Because of the auto-fill thing, we should not allow direct access to
222
- # the tags hash. Instead, the methods above should be used to do
223
- # operations like add, delete, and fetch.
224
- #
225
- # CONTRAST-22914
226
- # please do NOT expose this w/ an attr_reader / accessor. there are
227
- # helper methods in this class that safely access the hash. the tags
228
- # method is private to avoid the side effect of a direct lookup with
229
- # `[]` adding an empty array to the hash.
230
- def tags
231
- @_tags ||= Hash.new { |h, k| h[k] = [] }
232
- end
233
-
234
104
  # Remove the tag ranges covering the given range
235
105
  def remove_tags range
236
106
  return unless tracked?
@@ -334,6 +204,19 @@ module Contrast
334
204
 
335
205
  private
336
206
 
207
+ # Because of the auto-fill thing, we should not allow direct access to
208
+ # the tags hash. Instead, the methods above should be used to do
209
+ # operations like add, delete, and fetch.
210
+ #
211
+ # CONTRAST-22914
212
+ # please do NOT expose this w/ an attr_reader / accessor. there are
213
+ # helper methods in this class that safely access the hash. the tags
214
+ # method is private to avoid the side effect of a direct lookup with
215
+ # `[]` adding an empty array to the hash.
216
+ def tags
217
+ @_tags ||= Hash.new { |h, k| h[k] = [] }
218
+ end
219
+
337
220
  # Given a tag, compare it to a given range and, if any part of that tag is within the range, return a new tag
338
221
  # covering the union of the original tag and the range. This new tag will start at the
339
222
  # max(tag.start, range.start) and end at min(tag.end, range.end)
@@ -35,6 +35,12 @@ module Contrast
35
35
  end
36
36
  end
37
37
 
38
+ def validate
39
+ return if class_name
40
+
41
+ raise(ArgumentError, "#{ node_class } #{ id } did not have a proper class name. Unable to create.")
42
+ end
43
+
38
44
  def module_names
39
45
  @_module_names ||= Set.new(deadzones.map(&:class_name))
40
46
  end
@@ -60,7 +60,7 @@ module Contrast
60
60
 
61
61
  digest = Contrast::Utils::Sha256Builder.instance.build_from_spec(spec)
62
62
  unless digest
63
- logger.debug('Unable to resolve digest for gem spec', spec: spec.to_s)
63
+ logger.debug('Unable to resolve digest for gem spec', spec: spec.to_s) if logger.debug?
64
64
  return
65
65
  end
66
66
  report_path = adjust_path_for_reporting(path, spec)
@@ -71,6 +71,7 @@ module Contrast
71
71
 
72
72
  # Populate the library_usages field of the Activity message using the data stored in the @gemdigest_cache.
73
73
  #
74
+ # TODO: RUBY-1355
74
75
  # @param activity [Contrast::Api::Dtm::Activity] the message to which to append the usage data
75
76
  def generate_library_usage activity
76
77
  return unless enabled?