Skip to content

Commit

Permalink
Don't overflow stack if method expectation for #respond_to? fails
Browse files Browse the repository at this point in the history
In the process of generating the error message for a failed method expectation,
ErrorGenerator would call Array#join on an array which contained the receiver.
Internally, Array#join calls #respond_to?(:to_str) on the objects which are being
joined into a string.

If the method expectation was for #respond_to?, the internal call to #respond_to?
could cause the expectation to fail again, causing an infinite loop and resulting
stack overflow.

Avoid this by not using Array#join to generate the error message. Fixes #1020.
  • Loading branch information
alexdowad committed Oct 20, 2015
1 parent b6ecc20 commit 3854903
Show file tree
Hide file tree
Showing 2 changed files with 22 additions and 6 deletions.
7 changes: 1 addition & 6 deletions lib/rspec/mocks/error_generator.rb
Original file line number Diff line number Diff line change
Expand Up @@ -69,12 +69,7 @@ def raise_similar_message_args_error(expectation, args_for_multiple_calls, backt
end

def default_error_message(expectation, expected_args, actual_args)
[
intro,
"received",
expectation.message.inspect,
unexpected_arguments_message(expected_args, actual_args),
].join(" ")
"#{intro} received #{expectation.message.inspect} #{unexpected_arguments_message(expected_args, actual_args)}"
end

# rubocop:disable Style/ParameterLists
Expand Down
21 changes: 21 additions & 0 deletions spec/rspec/mocks/matchers/receive_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -393,6 +393,27 @@ def receiver.method_missing(*a); end # a poor man's stub...
reset_all
end
end

context "when expected message is respond_to?" do
context "and conditions are placed on expected call" do
context "and the conditions fail... :(" do
it "does not result in infinite recursion and stack overflow" do
# Setting a method expectation causes the method to be proxied
# RSpec may call #respond_to? when processing a failed expectation
# If those internal calls go to the proxied method, that could
# result in another failed expectation error, causing infinite loop

expect {
obj = Object.new
expect(obj).to receive(:respond_to?).with('something highly unlikely')
obj.respond_to?(:not_what_we_wanted)
}.to raise_error(/received :respond_to\? with unexpected arguments/)

reset_all
end
end
end
end
end

describe "expect_any_instance_of(...).to receive" do
Expand Down

0 comments on commit 3854903

Please sign in to comment.