contrast-agent 4.10.0 → 4.13.1

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