Skip to content

Commit cbd20ec

Browse files
committed
[GR-18163] Avoid recursion when redefining Warning#warn (#2109)
PullRequest: truffleruby/2035
2 parents 20f25fc + 3d2a832 commit cbd20ec

File tree

4 files changed

+47
-4
lines changed

4 files changed

+47
-4
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ Compatibility:
4545
* Support refinements for `#to_s` called by string interpolation (#2110, @ssnickolay)
4646
* Module#using raises error in method scope (#2112, @ssnickolay)
4747
* `File#path` now returns a new mutable String on every call like MRI (#2115).
48+
* Avoid infinite recursion when redefining `Warning#warn` and calling `Kernel#warn` (#2109).
4849

4950
Performance:
5051

spec/ruby/core/kernel/warn_spec.rb

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -197,4 +197,35 @@ def o.to_sym; :deprecated; end
197197
-> { warn(**h) }.should_not complain(verbose: true)
198198
-> { warn('foo', **h) }.should complain("foo\n")
199199
end
200+
201+
it "does not call Warning.warn if self is the Warning module" do
202+
# RubyGems redefines Kernel#warn so we need to use a subprocess and disable RubyGems here
203+
code = <<-RUBY
204+
def Warning.warn(*args, **kwargs)
205+
raise 'should not be called'
206+
end
207+
Kernel.instance_method(:warn).bind(Warning).call('Kernel#warn spec edge case')
208+
RUBY
209+
out = ruby_exe(code, args: "2>&1", options: "--disable-gems")
210+
out.should == "Kernel#warn spec edge case\n"
211+
$?.should.success?
212+
end
213+
214+
it "avoids recursion if Warning#warn is redefined and calls super" do
215+
# This works because of the spec above, which is the workaround for it.
216+
# Note that redefining Warning#warn is a mistake which would naturally end in infinite recursion,
217+
# Warning.extend Module.new { def warn } should be used instead.
218+
# RubyGems redefines Kernel#warn so we need to use a subprocess and disable RubyGems here
219+
code = <<-RUBY
220+
module Warning
221+
def warn(*args)
222+
super
223+
end
224+
end
225+
warn "avoid infinite recursion"
226+
RUBY
227+
out = ruby_exe(code, args: "2>&1", options: "--disable-gems")
228+
out.should == "avoid infinite recursion\n"
229+
$?.should.success?
230+
end
200231
end

spec/tags/core/kernel/warn_tags.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,4 @@
11
slow:Kernel#warn :uplevel keyword argument shows the caller of #require and not #require itself without RubyGems
22
slow:Kernel#warn :uplevel keyword argument shows the caller of #require and not #require itself with RubyGems loaded
3+
slow:Kernel#warn does not call Warning.warn if self is the Warning module
4+
slow:Kernel#warn avoids recursion if Warning#warn is redefined and calls super

src/main/ruby/truffleruby/core/kernel.rb

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -643,7 +643,7 @@ def untrace_var(name, cmd=undefined)
643643
def warn(*messages, uplevel: undefined)
644644
if !Primitive.nil?($VERBOSE) && !messages.empty?
645645
prefix = if Primitive.undefined?(uplevel)
646-
+''
646+
''
647647
else
648648
uplevel = Primitive.rb_to_int(uplevel)
649649
raise ArgumentError, "negative level (#{uplevel})" unless uplevel >= 0
@@ -662,13 +662,22 @@ def warn(*messages, uplevel: undefined)
662662
if caller
663663
"#{caller.path}:#{caller.lineno}: warning: "
664664
else
665-
+'warning: '
665+
'warning: '
666666
end
667667
end
668668

669-
stringio = Truffle::StringOperations::SimpleStringIO.new(prefix)
669+
stringio = Truffle::StringOperations::SimpleStringIO.new(+prefix)
670670
Truffle::IOOperations.puts(stringio, *messages)
671-
Warning.warn(stringio.string)
671+
message = stringio.string
672+
673+
if Primitive.object_equal(self, Warning) # avoid recursion when redefining Warning#warn
674+
unless message.encoding.ascii_compatible?
675+
raise Encoding::CompatibilityError, "ASCII incompatible encoding: #{message.encoding}"
676+
end
677+
$stderr.write message
678+
else
679+
Warning.warn(message)
680+
end
672681
end
673682
nil
674683
end

0 commit comments

Comments
 (0)