Class: Contrast::Utils::ClassUtil

Inherits:
Object
  • Object
show all
Defined in:
lib/contrast/utils/class_util.rb

Overview

Utility methods for exploring the complete space of Objects

Class Method Summary collapse

Class Method Details

.descendants(mod) ⇒ Array<Module>

Given a module, return all of its descendants



16
17
18
# File 'lib/contrast/utils/class_util.rb', line 16

def descendants mod
  ObjectSpace.each_object(mod).to_a
end

.prepended?(mod, ancestors = nil) ⇒ Boolean

some classes have had things prepended to them, like Marshal in Rails 5 and higher. Their ActiveSupport::MarshalWithAutoloading will break our alias patching approach, as will any other prepend on something that we touch. Prepend and Alias are inherently incompatible monkey patching approaches. As such, we need to know if something has been prepended to.



31
32
33
34
# File 'lib/contrast/utils/class_util.rb', line 31

def prepended? mod, ancestors = nil
  ancestors ||= mod.ancestors
  ancestors[0] != mod
end

.prepended_method?(mod, method_policy) ⇒ Boolean

return true if the given method is overwritten by one of the ancestors in the ancestor change that comes before the given module



38
39
40
41
42
43
44
45
46
47
48
49
50
# File 'lib/contrast/utils/class_util.rb', line 38

def prepended_method? mod, method_policy
  target_module = determine_target_class mod, method_policy.instance_method
  ancestors = target_module.ancestors
  return false unless prepended?(target_module, ancestors)

  ancestors.each do |ancestor|
    break if ancestor == target_module

    methods = ancestor.instance_methods(false)
    return true if methods.include?(method_policy.method_name)
  end
  false
end

.to_contrast_string(object) ⇒ String

Return a String representing the object invoking this method in the form expected by our dataflow events.



58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
# File 'lib/contrast/utils/class_util.rb', line 58

def to_contrast_string object
  # Only treat object like a string if it actually is a string
  # some subclasses of String override string methods we depend on
  if object.cs__class == String
    cached = to_cached_string(object)
    return cached if cached

    object.dup
  elsif object.cs__is_a?(Symbol)
    ":#{ object }"
  elsif object.cs__is_a?(Numeric)
    object.to_s
  elsif object.cs__is_a?(Module) || object.cs__is_a?(Class)
    "#{ object.cs__name }@#{ object.__id__ }"
  elsif object.cs__is_a?(Regexp)
    object.source
  else
    "#{ object.cs__class.cs__name }@#{ object.__id__ }"
  end
end

.truly_defined?(name) ⇒ Boolean

The method const_defined? can cause autoload, which is bad for us. The method autoload? doesn’t traverse namespaces. This method lets us provide a constant, as a String, and parse it to determine if it has been truly truly defined, meaning it existed before this method was invoked, not as a result of it.

This is required to handle a bug in Ruby prior to 2.7.0. When we drop support for 2.6.X, we should remove this code. bugs.ruby-lang.org/issues/10741



90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
# File 'lib/contrast/utils/class_util.rb', line 90

def truly_defined? name
  return false unless name

  segments = name.split(Contrast::Utils::ObjectShare::DOUBLE_COLON)
  previous_module = Module
  segments.each do |segment|
    return false if previous_module.cs__autoload?(segment)
    return false unless previous_module.cs__const_defined?(segment)

    previous_module = previous_module.cs__const_get(segment)
  end

  true
rescue NameError # account for nonsense / poorly formatted constants
  false
end