lib/contrast/utils/class_util.rb in contrast-agent-4.11.0 vs lib/contrast/utils/class_util.rb in contrast-agent-4.12.0
- old
+ new
@@ -7,30 +7,33 @@
module Contrast
module Utils
# Utility methods for exploring the complete space of Objects
class ClassUtil
- @lru_cache = LRUCache.new
+ @lru_cache = LRUCache.new(300)
+ @string_cache = LRUCache.new(300)
class << self
- # 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.
+ # 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 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
- # return true if the given method is overwritten by one of the ancestors
- # in the ancestor change that comes before the given module
+ # return true if the given method is overwritten by one of the ancestors in the ancestor change that comes
+ # before the given module
+ #
+ # @param mod [Module] the Module to check to see if it has had something prepended
+ # @param method_policy [Contrast::Agent::Patching::Policy::MethodPolicy] the policy that holds the method we
+ # need to check
+ # @return [Boolean] if this method specifically was prepended
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)
@@ -41,53 +44,53 @@
return true if methods.include?(method_policy.method_name)
end
false
end
- # Return a String representing the object invoking this method in the
- # form expected by our dataflow events.
+ # Return a String representing the object invoking this method in the form expected by our dataflow events.
+ # After implementing the LRU Cache, we firstly need to check if already had that object cached and if we have
+ # it - we can return it directly; otherwise we'll calculate and store the result before returning.
#
+ # TODO: RUBY-1327
+ # Once we move to 2.7+, we can combine the caches using ID b/c the memory location stops being the id
+ #
# @param object [Object, nil] 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
- # After implementing the LRU Cache, we firstly need to check if already had that object cached
- # and if we have it - we can return it directly
- return @lru_cache[object.__id__] if @lru_cache.key? object.__id__
+ # 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
+ return @string_cache[object] if @string_cache.key? object
- # Only treat object like a string if it actually is a string+
- # some subclasses of String override string methods we depend on
- @lru_cache[object.__id__] = if object.cs__class == String
- cached = to_cached_string(object)
- return cached if cached
+ @string_cache[object] = to_cached_string(object) || object.dup
+ else
+ return @lru_cache[object.__id__] if @lru_cache.key? object.__id__
- object.dup
- elsif object.nil?
- Contrast::Utils::ObjectShare::NIL_STRING
- elsif object.cs__is_a?(Symbol)
- ":#{ object }"
- elsif object.cs__is_a?(Module) || object.cs__is_a?(Class)
- "#{ object.cs__name }@#{ object.__id__ }"
- elsif object.cs__is_a?(Regexp)
- object.source
- elsif use_to_s?(object)
- object.to_s
- else
- "#{ object.cs__class.cs__name }@#{ object.__id__ }"
- end
+ @lru_cache[object.__id__] = if object.nil?
+ Contrast::Utils::ObjectShare::NIL_STRING
+ elsif object.cs__is_a?(Symbol)
+ ":#{ object }"
+ elsif object.cs__is_a?(Module) || object.cs__is_a?(Class)
+ "#{ object.cs__name }@#{ object.__id__ }"
+ elsif object.cs__is_a?(Regexp)
+ object.source
+ elsif use_to_s?(object)
+ object.to_s
+ else
+ "#{ object.cs__class.cs__name }@#{ object.__id__ }"
+ end
+ end
end
- # 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.
+ # 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
+ # TODO: RUBY-1326
+ # 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
@@ -106,35 +109,39 @@
end
private
# Some objects have nice to_s that we can use to make them human readable. If they do, we should leverage them.
- # We used to do this by default, but this opened us up to danger, so we're instead using an allow list approach.
+ # We used to do this by default, but this opened us up to danger, so we're instead using an allow list
+ # approach.
#
# @param object [Object] something that may have a safe to_s method
# @return [Boolean] if we should invoke to_s to represent the object
def use_to_s? object
return true if object.cs__is_a?(Numeric)
return true if defined?(Arel::Nodes::SqlLiteral) && object.cs__is_a?(Arel::Nodes::SqlLiteral)
false
end
+ # Find the target class based on the instance, or module, provided. If a module, return it.
+ #
+ # @param mod [Module] the Module, or instance of a Module, that we need to check
+ # @param is_instance [Boolean] is the object provided an instance of a class, requiring lookup by class
+ # @return [Module]
def determine_target_class mod, is_instance
return mod if mod.singleton_class?
return mod.cs__singleton_class unless is_instance
mod
end
- # If the String matches a common String in our ObjectShare, return that
- # rather that for use as the representation of the String rather than
- # forcing a duplication of the String.
+ # If the String matches a common String in our ObjectShare, return that rather that for use as the
+ # representation of the String rather than forcing a duplication of the String.
#
- # @param string [String] some string of which we want a Contrast
- # representation.
- # @return [String,nil] the ObjectShare version of the String or nil
+ # @param string [String] some string of which we want a Contrast representation.
+ # @return [String, nil] the ObjectShare version of the String or nil
def to_cached_string string
return Contrast::Utils::ObjectShare::EMPTY_STRING if string.empty?
return Contrast::Utils::ObjectShare::SLASH if string == Contrast::Utils::ObjectShare::SLASH
return Contrast::Utils::ObjectShare::EQUALS if string == Contrast::Utils::ObjectShare::EQUALS