Skip to content

Commit

Permalink
Mixed in class methods detection should be best effort
Browse files Browse the repository at this point in the history
We try to do something sneaky to detect modules that are mixed into a
class through inclusion of another module, where we try to infer that
information from the change in the ancestor list of a test class after
including the said module to that class.

However, that operation is risky since a `self.included` method on the
target module could potentially run any Ruby code and it turns out some
code in the wild even try to require optional gems when the concern is
being included.

Since this detection is best effort, it should be fine to catch
all kinds of `Exception`s and bail out of the operation without
side-effects.

Fixes: #237
  • Loading branch information
paracycle committed Mar 9, 2021
1 parent ad0a33c commit 80fe745
Show file tree
Hide file tree
Showing 2 changed files with 60 additions and 3 deletions.
10 changes: 7 additions & 3 deletions lib/tapioca/compilers/symbol_table/symbol_generator.rb
Original file line number Diff line number Diff line change
Expand Up @@ -364,9 +364,13 @@ def method_missing(symbol, *args)
end

define_singleton_method(:include) do |mod|
before = singleton_class.ancestors
super(mod).tap do
mixins_from_modules[mod] = singleton_class.ancestors - before
begin
before = singleton_class.ancestors
super(mod).tap do
mixins_from_modules[mod] = singleton_class.ancestors - before
end
rescue Exception # rubocop:disable Lint/RescueException
# this is a best effort, bail if we can't perform this
end
end

Expand Down
53 changes: 53 additions & 0 deletions spec/tapioca/compilers/symbol_table_compiler_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -1662,6 +1662,59 @@ def included(base); end
assert_equal(output, compile)
end

it("safely bails out of generating mixes_in_class_methods for modules that do weird things") do
add_ruby_file("concern_that_requires_random_stuff.rb", <<~RUBY)
module ConcernThatRequiresRandomStuff
def self.included(base)
require "non_existent_require_path"
end
end
RUBY

add_ruby_file("concern_that_explicitly_raises.rb", <<~RUBY)
module ConcernThatExplicitlyRaises
def self.included(base)
raise "I ran into an exception case"
end
end
RUBY

add_ruby_file("concern_that_performs_an_illegal_operation.rb", <<~RUBY)
module ConcernThatPerfomsAnIllegalOperation
def self.included(base)
sum(2)
end
def self.sum(a, b)
a + b
end
end
RUBY

output = template(<<~RBI)
module ConcernThatExplicitlyRaises
class << self
def included(base); end
end
end
module ConcernThatPerfomsAnIllegalOperation
class << self
def included(base); end
def sum(a, b); end
end
end
module ConcernThatRequiresRandomStuff
class << self
def included(base); end
end
end
RBI

assert_equal(output, compile)
end

it("adds mixes_in_class_methods(ClassMethods) to modules that extend from ActiveSuport::Concern") do
add_ruby_file("active_support/concern.rb", <<~RUBY)
module ActiveSupport
Expand Down

0 comments on commit 80fe745

Please sign in to comment.