Module: InternalApi
- Extended by:
- InternalApi
- Included in:
- InternalApi
- Defined in:
- lib/internal_api.rb,
lib/internal_api/version.rb,
lib/internal_api/rewriter.rb,
lib/internal_api/full_method_source_location.rb
Overview
The InternalApi module provides one public method (‘.internal_api`) available on any Ruby module. This method takes as its single argument any object has public methods. When called, the (public) methods of the caller will no longer be directly accessible.
This is deliberately designed to not depend on any gems, C-extensions, or any Ruby features specific to a minor version.
Defined Under Namespace
Modules: FullMethodSourceLocation, Rewriter, RubyCoreExtension Classes: ViolationError
Constant Summary collapse
- LoaderMutex =
Mutex.new
- VERSION =
File.read(File.join(__dir__, '..', '..', 'VERSION'))
Instance Method Summary collapse
- #check_caller!(protector) ⇒ Object
- #debug(line) ⇒ Object
-
#protect(protectee, protector) ⇒ Object
Rewrites all public methods on the protectee (the Ruby class or module that received the ‘internal_api’ message), replacing them with a method that checks the backtrace and ensures at least one line matches one of the public methods of the protector.
- #public_method_cache ⇒ Object
- #rewrite_method!(protectee, internal_method, protector) ⇒ Object
Instance Method Details
#check_caller!(protector) ⇒ Object
50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 |
# File 'lib/internal_api.rb', line 50 def check_caller!(protector) allowed_caller_methods = InternalApi.public_method_cache[protector] # NB: `caller` is much slower than `caller_locations` caller_locations.each do |location| # This calculation is quadratic but as the backtrace is finite and these # comparisons take only tens of nanoseconds each this is fast enough for # production use. allowed_caller_methods.each do |path, range| if location.path == path && range.include?(location.lineno) return path, range end end end raise_violation!(caller_locations[1].label, protector) end |
#debug(line) ⇒ Object
83 84 85 |
# File 'lib/internal_api.rb', line 83 def debug(line) puts "InternalApi: #{line}" if $DEBUG end |
#protect(protectee, protector) ⇒ Object
Rewrites all public methods on the protectee (the Ruby class or module that received the ‘internal_api’ message), replacing them with a method that checks the backtrace and ensures at least one line matches one of the public methods of the protector.
35 36 37 38 39 40 41 42 43 44 45 46 47 48 |
# File 'lib/internal_api.rb', line 35 def protect(protectee, protector) calculate_public_methods!(protector) # Extract the eigenclass of any object # https://medium.com/@ethan.reid.roberts/rubys-anonymous-eigenclass-putting-the-ei-in-team-ebc1e8f8d668 eigenclass = (class << protectee; self; end) # Rewrite future public singleton methods Rewriter.add_singleton_rewrite_hooks!(protectee, protector) # Rewrite eigenclass' future public instance methods Rewriter.add_instance_rewrite_hooks!(eigenclass, protector) # Rewrite eigenclass' future public singleton methods Rewriter.add_singleton_rewrite_hooks!(eigenclass, protector) end |
#public_method_cache ⇒ Object
79 80 81 |
# File 'lib/internal_api.rb', line 79 def public_method_cache @public_method_cache ||= {} end |
#rewrite_method!(protectee, internal_method, protector) ⇒ Object
66 67 68 69 70 71 72 73 74 75 76 77 |
# File 'lib/internal_api.rb', line 66 def rewrite_method!(protectee, internal_method, protector) protectee.instance_eval do # We create a new pointer to the original method alias_method "_internal_api_#{internal_method}", internal_method # And overwrite it define_method internal_method do |*args, &block| InternalApi.check_caller!(protector) send("_internal_api_#{internal_method}", *args, &block) end end end |