lib/contrast/agent/patching/policy/patcher.rb in contrast-agent-4.3.2 vs lib/contrast/agent/patching/policy/patcher.rb in contrast-agent-4.4.0
- old
+ new
@@ -50,24 +50,25 @@
# Hook to install the Contrast changes needed to allow for the
# instrumentation of the application - this only occurs once, during
# startup to catchup on everything we didn't see get loaded
def patch
catchup_after_load_patches
- patch_methods
+ catchup_loaded_methods
Contrast::Agent::Assess::Policy::RewriterPatch.rewrite_interpolations
end
# Hook to only monkeypatch Contrast. This will not trigger any
# other functions, like rewriting or scanning. Exposed for those
# situations, like ActiveRecord dynamically defining functions,
# where only a subset of the Assess changes are needed.
PATCH_MONITOR = Monitor.new
- def patch_methods
+ # Iterate over and patch those Modules and Methods which were loaded before the Agent was enabled.
+ def catchup_loaded_methods
PATCH_MONITOR.synchronize do
t = Contrast::Agent::Thread.new do
- synchronized_patch_methods
+ synchronized_catchup_loaded_methods
end
# aborting on exception makes exceptions propagate.
t.abort_on_exception = true
t.priority = Thread.current.priority + 1
t.join
@@ -98,11 +99,11 @@
# class (or module) and the given method. Time to do some tracking.
#
# @param mod [Module] the module in which the patch should be
# placed.
# @param methods [Array(Symbol)] all the instance or singleton
- # methods in this clazz.
+ # methods in this mod.
# @param method_policy [Contrast::Agent::Patching::Policy::MethodPolicy]
# the policy that applies to the given method_name.
# @return [Boolean] if patched, either by this invocation or a
# previous, or not
def patch_method mod, methods, method_policy
@@ -147,11 +148,11 @@
# Hook to only monkeypatch Contrast. This will not trigger any
# other functions, like rewriting or scanning. This method should
# only be invoked by the patch_methods method above in order to
# ensure it is wrapped in a synchronize call
- def synchronized_patch_methods
+ def synchronized_catchup_loaded_methods
logger.trace_with_time('Running patching') do
patched = []
all_module_names.each do |patchable_name|
next if AGENT.skip_instrumentation?(patchable_name)
@@ -164,57 +165,41 @@
end
all_module_names.subtract(patched)
end
end
- # Given the patchers that apply to this class that may apply, patch
- # Contrast method calls into the methods for which we have rules.
+ # Given the patchers that apply to this class that may apply, patch Contrast method calls into the methods
+ # for which we have rules.
#
- # @param module_data [Contrast::Agent::ModuleData] the module, and
- # its name, that's being patched into
- # @param redo_patch [Boolean] a trigger to force patching
- # regardless of the state of the
- # Contrast::Agent::Patching::Policy::PatchStatus status on the
- # Module
+ # @param module_data [Contrast::Agent::ModuleData] the module, and its name, that's being patched into
+ # @param redo_patch [Boolean] a trigger to force patching regardless of the state of the
+ # Contrast::Agent::Patching::Policy::PatchStatus status on the Module
def patch_into_module module_data, redo_patch = false
status = status_type.get_status(module_data.mod)
return if (status&.patched? || status&.patching?) && !redo_patch
- # Begin patching our sources into the given clazz (or module)
- # Any patcher that has the name of the clazz will be evaluated for
- # patching.
- # Find all the patchers that apply to this class, sorted by type.
+ # Begin patching our sources into the given module. Any patcher that has the name of the module will be
+ # evaluated for patching. Find all the patchers that apply to this class, sorted by type.
module_policy = Contrast::Agent::Patching::Policy::ModulePolicy.create_module_policy(module_data.name)
+ # If there's nothing to match, then set that status and exit
+ if module_policy.empty?
+ status.no_patch!
+ return
+ end
- clazz = module_data.mod
-
status.patching!
- patched = false
+ num_applied_patches = patch_into_instance_methods(module_data, module_policy)
+ num_applied_patches += patch_into_singleton_methods(module_data, module_policy)
+ if adjust_for_prepend(module_data) ||
+ module_policy.num_expected_patches == num_applied_patches
- counts = 0
- # Monkey patch any methods in this class that have matching nodes in the policy
- unless module_policy.empty?
- instance_methods = all_instance_methods(clazz, true)
- singleton_methods = clazz.singleton_methods(false)
- counts += patch_into_methods(clazz, instance_methods, module_policy, true)
- counts += patch_into_methods(clazz, singleton_methods, module_policy, false)
- counts = module_policy.num_expected_patches if adjust_for_prepend(clazz)
- patched = true
- end
-
- if patched
- if module_policy.num_expected_patches == counts
- status.patched!
- else
- status.partial_patch!
- end
+ status.patched!
else
- status.no_patch!
+ status.partial_patch!
end
rescue StandardError => e
- status ||= status_type.get_status(module_data.mod)
- status.failed_patch!
+ status&.failed_patch!
logger.warn('Patching failed', e, module: module_data.name)
ensure
logger.trace('Patching complete',
module: module_data.name,
result: Contrast::Agent::Patching::Policy::PatchStatus.get_status(
@@ -247,10 +232,33 @@
instance_methods << :dup
end
instance_methods
end
+ # Patch into the Instance Methods, including private, of the given Module that match the ModulePolicy
+ # provided.
+ #
+ # @param module_data [Contrast::Agent::ModuleData] the module, and its name, that's being patched into
+ # @param module_policy [Contrast::Agent::Patching::Policy::ModulePolicy] All the patchers that apply to
+ # this module, sorted by type.
+ def patch_into_instance_methods module_data, module_policy
+ mod = module_data.mod
+ methods = all_instance_methods(mod, true)
+ patch_into_methods(mod, methods, module_policy, true)
+ end
+
+ # Patch into the Singleton Methods of the given Module that match the ModulePolicy provided.
+ #
+ # @param module_data [Contrast::Agent::ModuleData] the module, and its name, that's being patched into
+ # @param module_policy [Contrast::Agent::Patching::Policy::ModulePolicy] All the patchers that apply to
+ # this module, sorted by type.
+ def patch_into_singleton_methods module_data, module_policy
+ mod = module_data.mod
+ methods = mod.singleton_methods(false)
+ patch_into_methods(mod, methods, module_policy, false)
+ end
+
# We've found the patchers that apply to this class (or module). Now we'll
# filter on the given method.
#
# @param mod [Module] The module from which to retrieve instance
# methods.
@@ -277,14 +285,13 @@
# previously included Module being prepended - the class that has
# already included the Module does not learn about the prepend and
# it has to be reapplied.
# TODO: RUBY-620 should remove the need for this
#
- # @param mod[Module] the Module for which a prepend action needs to
- # be accounted
+ # @param module_data [Contrast::Agent::ModuleData] the module, and its name, that's being patched into
# @return [Boolean] if an adjustment was made or not
- def adjust_for_prepend mod
- return false unless mod.cs__name == 'CGI::Util'
+ def adjust_for_prepend module_data
+ return false unless module_data.mod.cs__name == 'CGI::Util'
CGI.include(CGI::Util)
CGI.extend(CGI::Util)
true
end