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