lib/contrast/utils/class_util.rb in contrast-agent-3.8.5 vs lib/contrast/utils/class_util.rb in contrast-agent-3.9.0
- old
+ new
@@ -1,58 +1,113 @@
# Copyright (c) 2020 Contrast Security, Inc. See https://www.contrastsecurity.com/enduser-terms-0317a for more details.
# frozen_string_literal: true
-cs__scoped_require 'contrast/core_extensions/object'
+cs__scoped_require 'contrast/extensions/ruby_core/module'
module Contrast
module Utils
- # Utility methods for exploring the complete space of objects
+ # Utility methods for exploring the complete space of Objects
class ClassUtil
- def self.ancestors_of clazz
- arr = []
- ObjectSpace.each_object(Module) do |obj|
- next unless obj <= clazz
+ class << self
+ # Given a module, return all of its descendants
+ #
+ # @param mod [Module] the module whose descendants you want to find.
+ # @return [Array<Module>] those Modules that inherit from the given.
+ def descendants mod
+ ObjectSpace.each_object(mod).to_a
+ end
- arr << obj
+ # 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.
+ #
+ # @param mod [Module] the Module to check to see if it has had something
+ # prepended
+ # @param ancestors [Array<Module>] the array of ancestors for the mod
+ # @return [Boolean] if the mod has been prepended or not
+ def prepended? mod, ancestors = nil
+ ancestors ||= mod.ancestors
+ ancestors[0] != mod
end
- arr
- end
- # 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.
- #
- # return true if the given module is not its first ancestor
- def self.prepended? mod, ancestors = nil
- ancestors ||= mod.ancestors
- ancestors[0] != mod
- end
+ # return true if the given method is overwritten by one of the ancestors
+ # in the ancestor change that comes before the given module
+ 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)
- def self.determine_target_class mod, is_instance
- return mod if mod.singleton_class?
+ ancestors.each do |ancestor|
+ break if ancestor == target_module
- return mod.cs__singleton_class unless is_instance
+ methods = ancestor.instance_methods(false)
+ return true if methods.include?(method_policy.method_name)
+ end
+ false
+ end
- mod
- end
+ # Return a String representing the object invoking this method in the
+ # form expected by our dataflow events.
+ #
+ # @param object [Object] the entity to convert to a String
+ # @return [String] the human readable form of the String, as defined by
+ # https://bitbucket.org/contrastsecurity/assess-specifications/src/master/vulnerability/capture-snapshot.md
+ def to_contrast_string object
+ if object.cs__is_a?(String)
+ return Contrast::Utils::ObjectShare::EMPTY_STRING if object.empty?
- # return true if the given method is overwritten by one of the ancestors
- # in the ancestor change that comes before the given module
- def self.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)
+ 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__ }"
+ else
+ "#{ object.cs__class.cs__name }@#{ object.__id__ }"
+ end
+ end
- ancestors.each do |ancestor|
- break if ancestor == target_module
+ # 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.
+ # https://bugs.ruby-lang.org/issues/10741
+ # @param name [String] the name of the constant to look up
+ # @return [Boolean]
+ def truly_defined? name
+ return false unless name
- methods = ancestor.instance_methods(false)
- return true if methods.include?(method_policy.method_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
- false
+
+ private
+
+ def determine_target_class mod, is_instance
+ return mod if mod.singleton_class?
+
+ return mod.cs__singleton_class unless is_instance
+
+ mod
+ end
end
end
end
end