@@ -85,18 +85,7 @@ def to_h
8585 end
8686 end
8787
88- Function = Struct . new ( :package , :cls , :labels , :function_names ) do # :nodoc:
89- def to_h
90- {
91- package : package ,
92- class : cls ,
93- labels : labels ,
94- functions : function_names . map ( &:to_sym )
95- } . compact
96- end
97- end
98- private_constant :Function
99-
88+ # Identifies specific methods within a package which should be hooked.
10089 class TargetMethods # :nodoc:
10190 attr_reader :method_names , :package
10291
@@ -118,28 +107,91 @@ def to_h
118107 end
119108 private_constant :TargetMethods
120109
121- OPENSSL_PACKAGES = -> ( labels ) { Package . build_from_path ( 'openssl' , package_name : 'openssl' , labels : labels ) }
110+ # Function represents a specific function configured for hooking by the +functions+
111+ # entry in appmap.yml. When the Config is initialized, each Function is converted into
112+ # a Package and TargetMethods. It's called a Function rather than a Method, because Function
113+ # is the AppMap terminology.
114+ Function = Struct . new ( :package , :cls , :labels , :function_names ) do # :nodoc:
115+ def to_h
116+ {
117+ package : package ,
118+ class : cls ,
119+ labels : labels ,
120+ functions : function_names . map ( &:to_sym )
121+ } . compact
122+ end
123+ end
124+ private_constant :Function
122125
123- # Methods that should always be hooked, with their containing
124- # package and labels that should be applied to them.
125- HOOKED_METHODS = {
126- 'ActionView::Renderer' => TargetMethods . new ( :render , Package . build_from_gem ( 'actionview' , shallow : false , package_name : 'action_view' , labels : %w[ mvc.view ] , optional : true ) . tap do |package |
127- package . handler_class = AppMap ::Handler ::Rails ::Template ::RenderHandler if package
128- end ) ,
129- 'ActionView::Resolver' => TargetMethods . new ( %i[ find_all find_all_anywhere ] , Package . build_from_gem ( 'actionview' , shallow : false , package_name : 'action_view' , labels : %w[ mvc.template.resolver ] , optional : true ) . tap do |package |
130- package . handler_class = AppMap ::Handler ::Rails ::Template ::ResolverHandler if package
131- end ) ,
132- 'ActionDispatch::Request::Session' => TargetMethods . new ( %i[ destroy [] dig values []= clear update delete fetch merge ] , Package . build_from_gem ( 'actionpack' , shallow : false , package_name : 'action_dispatch' , labels : %w[ http.session ] , optional : true ) ) ,
133- 'ActionDispatch::Cookies::CookieJar' => TargetMethods . new ( %i[ []= clear update delete recycle ] , Package . build_from_gem ( 'actionpack' , shallow : false , package_name : 'action_dispatch' , labels : %w[ http.cookie ] , optional : true ) ) ,
134- 'ActionDispatch::Cookies::EncryptedCookieJar' => TargetMethods . new ( %i[ []= ] , Package . build_from_gem ( 'actionpack' , shallow : false , package_name : 'action_dispatch' , labels : %w[ http.cookie crypto.encrypt ] , optional : true ) ) ,
135- 'CanCan::ControllerAdditions' => TargetMethods . new ( %i[ authorize! can? cannot? ] , Package . build_from_gem ( 'cancancan' , shallow : false , labels : %w[ security.authorization ] , optional : true ) ) ,
136- 'CanCan::Ability' => TargetMethods . new ( %i[ authorize! ] , Package . build_from_gem ( 'cancancan' , shallow : false , labels : %w[ security.authorization ] , optional : true ) ) ,
137- 'ActionController::Instrumentation' => [
138- TargetMethods . new ( %i[ process_action send_file send_data redirect_to ] , Package . build_from_gem ( 'actionpack' , shallow : false , package_name : 'action_controller' , labels : %w[ mvc.controller ] , optional : true ) )
139- ]
140- } . freeze
126+ ClassTargetMethods = Struct . new ( :cls , :target_methods ) # :nodoc:
127+ private_constant :ClassTargetMethods
128+
129+ MethodHook = Struct . new ( :cls , :method_names , :labels ) # :nodoc:
130+ private_constant :MethodHook
131+
132+ class << self
133+ def package_hooks ( gem_name , methods , handler_class : nil , package_name : nil )
134+ Array ( methods ) . map do |method |
135+ package = Package . build_from_gem ( gem_name , package_name : package_name , labels : method . labels , shallow : false , optional : true )
136+ next unless package
137+
138+ package . handler_class = handler_class if handler_class
139+ ClassTargetMethods . new ( method . cls , TargetMethods . new ( Array ( method . method_names ) , package ) )
140+ end . compact
141+ end
142+
143+ def method_hook ( cls , method_names , labels )
144+ MethodHook . new ( cls , method_names , labels )
145+ end
146+ end
147+
148+ # Hook well-known functions. When a function configured here is available in the bundle, it will be hooked with the
149+ # predefined labels specified here. If any of these hooks are not desired, they can be disabled in the +exclude+ section
150+ # of appmap.yml.
151+ METHOD_HOOKS = [
152+ package_hooks ( 'actionview' ,
153+ [
154+ method_hook ( 'ActionView::Renderer' , :render , %w[ mvc.view ] ) ,
155+ method_hook ( 'ActionView::TemplateRenderer' , :render , %w[ mvc.view ] ) ,
156+ method_hook ( 'ActionView::PartialRenderer' , :render , %w[ mvc.view ] )
157+ ] ,
158+ handler_class : AppMap ::Handler ::Rails ::Template ::RenderHandler ,
159+ package_name : 'action_view'
160+ ) ,
161+ package_hooks ( 'actionview' ,
162+ [
163+ method_hook ( 'ActionView::Resolver' , %i[ find_all find_all_anywhere ] , %w[ mvc.template.resolver ] )
164+ ] ,
165+ handler_class : AppMap ::Handler ::Rails ::Template ::ResolverHandler ,
166+ package_name : 'action_view'
167+ ) ,
168+ package_hooks ( 'actionpack' ,
169+ [
170+ method_hook ( 'ActionDispatch::Request::Session' , %i[ destroy [] dig values []= clear update delete fetch merge ] , %w[ http.session ] ) ,
171+ method_hook ( 'ActionDispatch::Cookies::CookieJar' , %i[ []= clear update delete recycle ] , %w[ http.session ] ) ,
172+ method_hook ( 'ActionDispatch::Cookies::EncryptedCookieJar' , %i[ []= clear update delete recycle ] , %w[ http.cookie crypto.encrypt ] )
173+ ] ,
174+ package_name : 'action_dispatch'
175+ ) ,
176+ package_hooks ( 'cancancan' ,
177+ [
178+ method_hook ( 'CanCan::ControllerAdditions' , %i[ authorize! can? cannot? ] , %w[ security.authorization ] ) ,
179+ method_hook ( 'CanCan::Ability' , %i[ authorize? ] , %w[ security.authorization ] )
180+ ]
181+ ) ,
182+ package_hooks ( 'actionpack' ,
183+ [
184+ method_hook ( 'ActionController::Instrumentation' , %i[ process_action send_file send_data redirect_to ] , %w[ mvc.controller ] )
185+ ] ,
186+ package_name : 'action_controller'
187+ )
188+ ] . flatten . freeze
141189
142- BUILTIN_METHODS = {
190+ OPENSSL_PACKAGES = -> ( labels ) { Package . build_from_path ( 'openssl' , package_name : 'openssl' , labels : labels ) }
191+
192+ # Hook functions which are builtin to Ruby. Because they are builtins, they may be loaded before appmap.
193+ # Therefore, we can't rely on TracePoint to report the loading of this code.
194+ BUILTIN_HOOKS = {
143195 'OpenSSL::PKey::PKey' => TargetMethods . new ( :sign , OPENSSL_PACKAGES . ( %w[ crypto.pkey ] ) ) ,
144196 'OpenSSL::X509::Request' => TargetMethods . new ( %i[ sign verify ] , OPENSSL_PACKAGES . ( %w[ crypto.x509 ] ) ) ,
145197 'OpenSSL::PKCS5' => TargetMethods . new ( %i[ pbkdf2_hmac_sha1 pbkdf2_hmac ] , OPENSSL_PACKAGES . ( %w[ crypto.pkcs5 ] ) ) ,
@@ -166,26 +218,29 @@ def to_h
166218 'JSON::Ext::Generator::State' => TargetMethods . new ( :generate , Package . build_from_path ( 'json' , package_name : 'json' , labels : %w[ format.json ] ) ) ,
167219 } . freeze
168220
169- attr_reader :name , :packages , :exclude , :hooked_methods , :builtin_methods
221+ attr_reader :name , :packages , :exclude , :hooked_methods , :builtin_hooks
170222
171223 def initialize ( name , packages , exclude : [ ] , functions : [ ] )
172224 @name = name
173225 @packages = packages
174- @hook_paths = packages . map ( &:path )
226+ @hook_paths = Set . new ( packages . map ( &:path ) )
175227 @exclude = exclude
176- @builtin_methods = BUILTIN_METHODS
228+ @builtin_hooks = BUILTIN_HOOKS
177229 @functions = functions
178- @hooked_methods = HOOKED_METHODS . dup
230+
231+ @hooked_methods = METHOD_HOOKS . each_with_object ( Hash . new { |h , k | h [ k ] = [ ] } ) do |cls_target_methods , hooked_methods |
232+ hooked_methods [ cls_target_methods . cls ] << cls_target_methods . target_methods
233+ end
234+
179235 functions . each do |func |
180236 package_options = { }
181237 package_options [ :labels ] = func . labels if func . labels
182- @hooked_methods [ func . cls ] ||= [ ]
183238 @hooked_methods [ func . cls ] << TargetMethods . new ( func . function_names , Package . build_from_path ( func . package , package_options ) )
184239 end
185240
186241 @hooked_methods . each_value do |hooks |
187242 Array ( hooks ) . each do |hook |
188- @hook_paths << hook . package . path if hook . package
243+ @hook_paths << hook . package . path
189244 end
190245 end
191246 end
0 commit comments