Class: Contrast::Agent::Assess::Finalizers::Hash

Inherits:
Hash
  • Object
show all
Defined in:
lib/contrast/agent/assess/finalizers/hash.rb

Overview

An extension of Hash that doesn’t impact GC of the object being stored by storing its ID as a Key to lookup and registering a finalizer on the object to remove its entry from the Hash immediately after it’s GC’d.

Constant Summary collapse

FROZEN_FINALIZED_IDS =
Set.new

Instance Method Summary collapse

Instance Method Details

#[](key) ⇒ Object



28
29
30
# File 'lib/contrast/agent/assess/finalizers/hash.rb', line 28

def [] key
  super key.__id__
end

#[]=(key, obj) ⇒ Object



17
18
19
20
21
22
23
24
25
26
# File 'lib/contrast/agent/assess/finalizers/hash.rb', line 17

def []= key, obj
  # We can't finalize frozen things, so only act on those that went
  # through .pre_freeze
  if key.cs__frozen?
    return unless FROZEN_FINALIZED_IDS.include?(key.__id__)
  else
    ObjectSpace.define_finalizer(key, finalize(key.__id__))
  end
  super key.__id__, obj
end

#finalize(key_id) ⇒ Object

Remove the given key from our frozen and properties tracking during finalization of the Object to which the given key_id pertains. NOTE: by necessity, this is the only method which takes the __id__, not the Object itself. You CANNOT pass the Object to this as a finalizer cannot hold reference to the Object being finalized; that prevents GC, which introduces a memory leak and defeats the entire purpose of this.

Parameters:

  • key_id (Integer)

    the Object Identifier to clean up during finalization.



69
70
71
72
73
74
# File 'lib/contrast/agent/assess/finalizers/hash.rb', line 69

def finalize key_id
  proc do
    FROZEN_FINALIZED_IDS.delete(key_id)
    delete(key_id)
  end
end

#pre_freeze(key) ⇒ Object

Frozen things cannot be finalized. To avoid any issue here, we intercept the #freeze call and set finalizers on the Object. To ensure later we know it’s been pre-finalized, we add it’s __id__ to our tracking.

Parameters:

  • key (Object)

    the Object on which we need to pre-define finalizers



83
84
85
86
87
88
89
90
91
92
# File 'lib/contrast/agent/assess/finalizers/hash.rb', line 83

def pre_freeze key
  return if key.cs__frozen?
  return if FROZEN_FINALIZED_IDS.include?(key.__id__)

  ObjectSpace.define_finalizer(key, finalize(key.__id__))

  FROZEN_FINALIZED_IDS << key.__id__
rescue StandardError => _e
  nil
end

#trackable?(key) ⇒ Boolean

Something is trackable if it is not a collection and either not frozen or it was frozen after we put a finalizer on it.

Parameters:

  • key (Object)

    the thing to determine if trackable

Returns:

  • (Boolean)


37
38
39
40
41
42
43
44
45
46
47
# File 'lib/contrast/agent/assess/finalizers/hash.rb', line 37

def trackable? key
  # Track things in these, not them themselves.
  return false if Contrast::Utils::DuckUtils.iterable_hash?(key)
  return false if Contrast::Utils::DuckUtils.iterable_enumerable?(key)
  # If it's not frozen, we can finalize/ track it.
  return true unless key.cs__frozen?

  # Otherwise, we can only track it if we've finalized it in our
  # freeze patch.
  FROZEN_FINALIZED_IDS.include?(key.__id__)
end

#tracked?(key) ⇒ Boolean

Determine if the given Object is tracked, meaning it has a known set of properties and those properties are tracked.

Parameters:

  • key (Object)

    the Object whose properties, by id, we want to check for tracked status

Returns:

  • (Boolean)


55
56
57
# File 'lib/contrast/agent/assess/finalizers/hash.rb', line 55

def tracked? key
  key?(key.__id__) && fetch(key.__id__, nil)&.tracked?
end