# 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' module Contrast module Utils # 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 arr << obj 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 def self.determine_target_class mod, is_instance return mod if mod.singleton_class? return mod.cs__singleton_class unless is_instance 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 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) 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 end end end