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

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_cacheObject



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