Module: Contrast::Agent::Assess::Property::Tagged

Included in:
Contrast::Agent::Assess::Properties
Defined in:
lib/contrast/agent/assess/property/tagged.rb

Overview

This module serves to hold the functionality required for the management of our dataflow tags.

Instance Method Summary collapse

Instance Method Details

#add_tag(label, range) ⇒ Object

Given a tag name and range object, add a new tag to this collection. If the given range touches an existing tag, we’ll combine the two, adjusting the existing one and dropping this new one.

Parameters:

  • label (String)

    the name of the tag

  • range (Range)

    the Range that the tag covers, inclusive to exclusive



126
127
128
129
130
131
# File 'lib/contrast/agent/assess/property/tagged.rb', line 126

def add_tag label, range
  length = range.end - range.begin
  tag = Contrast::Agent::Assess::Tag.new(label, length, range.begin)
  existing = fetch_tag(label)
  tags[label] = Contrast::Utils::TagUtil.ordered_merge(existing, tag)
end

#any_tags_between?(start, finish) ⇒ Boolean

Similar to #tracked?, but limited to a given range.

Parameters:

  • start (Integer)

    the inclusive start index to check.

  • finish (Integer)

    the exclusive end index to check.

Returns:

  • (Boolean)


39
40
41
42
43
44
45
46
# File 'lib/contrast/agent/assess/property/tagged.rb', line 39

def any_tags_between? start, finish
  return false unless tracked?

  tags.each_value do |tag_array|
    return true if tag_array.any? { |tag| tag.overlaps?(start, finish) }
  end
  false
end

#cleanup_tagsObject

Calls merge to combine touching or overlapping tags Deletes empty tags



157
158
159
160
161
162
# File 'lib/contrast/agent/assess/property/tagged.rb', line 157

def cleanup_tags
  return unless tracked?

  Contrast::Utils::TagUtil.merge_tags(tags)
  tags.delete_if { |_, value| value.empty? }
end

#clear_tagsObject

Reset the tag hash



143
144
145
# File 'lib/contrast/agent/assess/property/tagged.rb', line 143

def clear_tags
  tags.clear if tracked?
end

#delete_tags(label) ⇒ Object

Remove all tags with a given label



138
139
140
# File 'lib/contrast/agent/assess/property/tagged.rb', line 138

def delete_tags label
  tags.delete(label) if tracked?
end

#delete_tags_at_ranges(ranges, shift = true) ⇒ Object

Remove all tags within the given ranges. This does not delete an entire tag if part of that tag is outside this range, meaning we may reduce sizes of tags or split them.

If shift is true, it is assumed the characters at those ranges were removed. If shift is false, it is assumed those ranges were replaced by the same number of characters and no shift is needed.

current tags: 0-15 range: 5-10 result: 0-5, 10-15

Parameters:

  • ranges (Array<Range>)

    the ranges to delete

  • shift (Boolean) (defaults to: true)

    move remaining tags to the left to account for the deletion



200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
# File 'lib/contrast/agent/assess/property/tagged.rb', line 200

def delete_tags_at_ranges ranges, shift = true
  return unless tracked?

  # Stage one - delete the tags w/o changing their
  # location.
  ranges.each do |range|
    remove_tags(range)
  end
  return unless shift

  # the amount we've already removed from the string
  shift = 0
  # Stage two - shift the tags to the left to account
  # for the sections that were deleted.
  ranges.each do |range|
    shift_tags_for_deletion(range, shift)
    shift += (range.end - range.begin)
  end

  # Clean up and merge any touching tags
  Contrast::Utils::TagUtil.merge_tags(tags)
end

#fetch_tag(label) ⇒ Array<Contrast::Agent::Assess::Tag>

We’ll use this as a helper method to retrieve tags from the hash. Because the hash auto-populates an empty array when we try to access a tag in it, we cannot use the [] method without side effect. To get around this, we’ll use a fetch work around.

Parameters:

  • label (Symbol)

    the label to look up

Returns:



172
173
174
# File 'lib/contrast/agent/assess/property/tagged.rb', line 172

def fetch_tag label
  tags.fetch(label, nil) if tracked?
end

#remove_tags(range) ⇒ Object

Remove the tag ranges covering the given range



252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
# File 'lib/contrast/agent/assess/property/tagged.rb', line 252

def remove_tags range
  return unless tracked?

  full_delete = []
  tags.each_pair do |key, value|
    remove = []
    add = []
    value.each do |tag|
      comparison = tag.compare_range(range.begin, range.end)
      # ABOVE and BELOW are not affected by this check
      case comparison
      when Contrast::Agent::Assess::Tag::LOW_SPAN
        tag.update_end(range.begin)
      when Contrast::Agent::Assess::Tag::WITHIN
        remove << tag
      when Contrast::Agent::Assess::Tag::WITHOUT
        new_tag = tag.clone
        new_tag.update_start(range.end)
        add << new_tag
        tag.update_end(range.begin)
      when Contrast::Agent::Assess::Tag::HIGH_SPAN
        tag.update_start(range.end)
      end
    end
    value.delete_if { |tag| remove.include?(tag) }
    Contrast::Utils::TagUtil.ordered_merge(value, add)
    full_delete << key if value.empty?
  end
  full_delete.each { |key| tags.delete(key) }
end

#set_tags(label, tag_ranges) ⇒ Object



133
134
135
# File 'lib/contrast/agent/assess/property/tagged.rb', line 133

def set_tags label, tag_ranges
  tags[label] = tag_ranges
end

#shift_tags(ranges) ⇒ Object

Shift all the tags in this object by the given ranges. This method assumes the ranges are sorted, meaning the leftmost (lowest) range is first

current tags: 0-15 range: 5-10 result: 0-5, 10-20



230
231
232
233
234
235
236
# File 'lib/contrast/agent/assess/property/tagged.rb', line 230

def shift_tags ranges
  return unless tracked?

  ranges.each do |range|
    shift_tags_for_insertion(range)
  end
end

#shift_tags_for_deletion(range, shift) ⇒ Object

Shift the tag ranges covering the given range We assume this is for a deletion, meaning we have to move tags to the left

Parameters:

  • range (Range)

    the range to delete

  • shift (Boolean)

    move remaining tags to the left to account for the deletion



289
290
291
292
293
294
295
296
297
298
299
300
301
302
# File 'lib/contrast/agent/assess/property/tagged.rb', line 289

def shift_tags_for_deletion range, shift
  return unless tracked?

  tags.each_value do |value|
    value.each do |tag|
      comparison = tag.compare_range(range.begin - shift, range.end - shift)
      # this is really the only thing we need to shift
      next unless comparison == Contrast::Agent::Assess::Tag::ABOVE

      length = range.end - range.begin
      tag.shift(0 - length)
    end
  end
end

#shift_tags_for_insertion(range) ⇒ Object

Shift the tag ranges covering the given range We assume this is for a insertion, meaning we have to move tags to the right



307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
# File 'lib/contrast/agent/assess/property/tagged.rb', line 307

def shift_tags_for_insertion range
  return unless tracked?

  tags.each_value do |value|
    add = []
    value.each do |tag|
      comparison = tag.compare_range(range.begin, range.end)
      length = range.end - range.begin
      # BELOW is not affected by this check
      case comparison
        # part of the tag is being inserted on
      when Contrast::Agent::Assess::Tag::LOW_SPAN
        new_tag = tag.clone
        new_tag.update_start(range.begin)
        new_tag.shift(length)
        add << new_tag
        tag.update_end(range.begin)
        # the tag exists in the inserted range. it is partially shifted
      when Contrast::Agent::Assess::Tag::WITHIN
        tag.shift(length)
        # the tag spans the range. leave the part below alone
      when Contrast::Agent::Assess::Tag::WITHOUT
        new_tag = tag.clone
        new_tag.update_start(range.begin)
        new_tag.shift(length)
        add << new_tag
        tag.update_end(range.begin)
      when Contrast::Agent::Assess::Tag::HIGH_SPAN, Contrast::Agent::Assess::Tag::ABOVE
        tag.shift(length)
      end
    end
    Contrast::Utils::TagUtil.ordered_merge(value, add)
  end
end

#tag_keysObject

Returns a list of all current tag labels, most likely to be used for a splat operation



149
150
151
152
153
# File 'lib/contrast/agent/assess/property/tagged.rb', line 149

def tag_keys
  return Contrast::Utils::ObjectShare::EMPTY_ARRAY unless tracked?

  tags.keys
end

#tagged?(label) ⇒ Boolean

Is the given tag present? Used in testing, so found by ‘be_tagged`, if you’re grepping for it

Parameters:

  • label (Symbol)

    the tag to check for

Returns:

  • (Boolean)


30
31
32
# File 'lib/contrast/agent/assess/property/tagged.rb', line 30

def tagged? label
  tracked? && tags.key?(label)
end

#tagsObject

Because of the auto-fill thing, we should not allow direct access to the tags hash. Instead, the methods above should be used to do operations like add, delete, and fetch.

CONTRAST-22914 please do NOT expose this w/ an attr_reader / accessor. there are helper methods in this class that safely access the hash. the tags method is private to avoid the side effect of a direct lookup with ‘[]` adding an empty array to the hash.



247
248
249
# File 'lib/contrast/agent/assess/property/tagged.rb', line 247

def tags
  @_tags ||= Hash.new { |h, k| h[k] = [] }
end

#tags_at(idx) ⇒ Array<Contrast::Agent::Assess::Tag>

Find all of the ranges that span a given index. This is used in propagation when we need to shift tags about. For instance, in the append method when we need to see if any tag at the end needs to be expanded out to the size of the new String.

Note: Tags do not know their key, so this is only the range covered

Parameters:

  • idx (Integer)

    the index to check for tags

Returns:



58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
# File 'lib/contrast/agent/assess/property/tagged.rb', line 58

def tags_at idx
  return Contrast::Utils::ObjectShare::EMPTY_ARRAY unless tracked?

  at = []
  tags.each_value do |tag_array|
    tag_array.each do |tag|
      if tag.covers?(idx)
        at << tag
      elsif tag.above?(idx)
        break
      end
    end
  end
  at
end

#tags_at_range(range) ⇒ Hash{String => Contrast::Agent::Assess::Tag}

given a range, select all tags in that range the selected tags are shifted such that the start index of the new tag (0) aligns with the given start index in the range

current tags: 5-15 range : 5-10 result : 0-05

Parameters:

  • range (Range)

    the span to check, inclusive to exclusive

Returns:



85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
# File 'lib/contrast/agent/assess/property/tagged.rb', line 85

def tags_at_range range
  return Contrast::Utils::ObjectShare::EMPTY_HASH unless tracked?

  at = Hash.new { |h, k| h[k] = [] }
  tags.each_pair do |key, value|
    add = []
    value.each do |tag|
      comparison = tag.compare_range(range.begin, range.end)
      # BELOW and ABOVE are applicable to this check and are removed.
      case comparison
        # part of the tag is being selected
      when Contrast::Agent::Assess::Tag::LOW_SPAN
        add << Contrast::Agent::Assess::Tag.new(tag.label, range.size)
        # the tag exists in the requested range, figure out the boundaries
      when Contrast::Agent::Assess::Tag::WITHIN
        start = tag.start_idx - range.begin
        finish = range.size - start
        add << Contrast::Agent::Assess::Tag.new(tag.label, finish, start)
        # the tag spans the requested range.
      when Contrast::Agent::Assess::Tag::WITHOUT
        add << Contrast::Agent::Assess::Tag.new(tag.label, range.size)
        # part of the tag is being selected
      when Contrast::Agent::Assess::Tag::HIGH_SPAN
        add << Contrast::Agent::Assess::Tag.new(tag.label, range.size)
      end
    end
    next if add.empty?

    at[key] = add
  end
  at
end

#tags_to_dtmArray<Contrast::Api::Dtm::TraceTaintRange>

Convert the tags of this object into the TraceTaintRange required to be sent to the service



180
181
182
# File 'lib/contrast/agent/assess/property/tagged.rb', line 180

def tags_to_dtm
  Contrast::Api::Dtm::TraceTaintRange.build_for_event(tags)
end

#tracked?Boolean

Is any tag present? Creating Tags is expensive and we check for Tags all the time on untracked things. ALWAYS!!! call this method before checking if an object has tags

Returns:

  • (Boolean)


21
22
23
# File 'lib/contrast/agent/assess/property/tagged.rb', line 21

def tracked?
  instance_variable_defined?(:@_tags) && tags.any?
end