Skip to content

Commit e36b2ff

Browse files
committed
[GR-18163] Fix "circular causes" error at raising exception
PullRequest: truffleruby/4547
2 parents e369f61 + 10e31e9 commit e36b2ff

File tree

8 files changed

+121
-11
lines changed

8 files changed

+121
-11
lines changed

spec/ruby/core/exception/full_message_spec.rb

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -211,4 +211,16 @@ class << e
211211
e.full_message(highlight: false).lines.first.should =~ /RuntimeError/
212212
e.full_message(highlight: true).lines.first.should =~ /#{Regexp.escape("\e[1;4mRuntimeError\e[m")}/
213213
end
214+
215+
it "allows cause with empty backtrace" do
216+
begin
217+
raise RuntimeError.new("Some runtime error"), cause: RuntimeError.new("Some other runtime error")
218+
rescue => e
219+
end
220+
221+
full_message = e.full_message
222+
full_message.should include "RuntimeError"
223+
full_message.should include "Some runtime error"
224+
full_message.should include "Some other runtime error"
225+
end
214226
end

spec/ruby/core/kernel/raise_spec.rb

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -203,6 +203,65 @@
203203
e.cause.should == e1
204204
end
205205
end
206+
207+
it "re-raises a previously rescued exception that doesn't have a cause and isn't a cause of any other exception with setting a cause implicitly" do
208+
begin
209+
begin
210+
raise "Error 1"
211+
rescue => e1
212+
begin
213+
raise "Error 2"
214+
rescue => e2
215+
raise "Error 3"
216+
end
217+
end
218+
rescue => e
219+
e.message.should == "Error 3"
220+
e.cause.should == e2
221+
end
222+
end
223+
224+
it "re-raises a previously rescued exception that doesn't have a cause and is a cause of other exception without setting a cause implicitly" do
225+
begin
226+
begin
227+
raise "Error 1"
228+
rescue => e1
229+
begin
230+
raise "Error 2"
231+
rescue => e2
232+
e1.cause.should == nil
233+
e2.cause.should == e1
234+
raise e1
235+
end
236+
end
237+
rescue => e
238+
e.should == e1
239+
e.cause.should == nil
240+
end
241+
end
242+
243+
it "re-raises a previously rescued exception that has a cause but isn't a cause of any other exception without setting a cause implicitly" do
244+
begin
245+
begin
246+
raise "Error 1"
247+
rescue => e1
248+
begin
249+
raise "Error 2"
250+
rescue => e2
251+
begin
252+
raise "Error 3", cause: RuntimeError.new("Error 4")
253+
rescue => e3
254+
e2.cause.should == e1
255+
e3.cause.should_not == e2
256+
raise e2
257+
end
258+
end
259+
end
260+
rescue => e
261+
e.should == e2
262+
e.cause.should == e1
263+
end
264+
end
206265
end
207266

208267
describe "Kernel#raise" do

spec/ruby/shared/kernel/raise.rb

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,10 @@ def initialize
8888
-> { @object.raise(nil) }.should raise_error(TypeError, "exception class/object expected")
8989
end
9090

91+
it "raises a TypeError when passed a message and an extra argument" do
92+
-> { @object.raise("message", {cause: RuntimeError.new()}) }.should raise_error(TypeError, "exception class/object expected")
93+
end
94+
9195
it "raises TypeError when passed a non-Exception object but it responds to #exception method that doesn't return an instance of Exception class" do
9296
e = Object.new
9397
def e.exception

src/main/java/org/truffleruby/core/exception/ExceptionNodes.java

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
*/
1010
package org.truffleruby.core.exception;
1111

12+
import com.oracle.truffle.api.dsl.Fallback;
1213
import com.oracle.truffle.api.interop.InteropLibrary;
1314
import com.oracle.truffle.api.library.CachedLibrary;
1415
import com.oracle.truffle.api.object.Shape;
@@ -368,4 +369,35 @@ Object getRaiseException(RubyException exception) {
368369

369370
}
370371

372+
@Primitive(name = "exception_used_as_a_cause?")
373+
public abstract static class IsUsedAsACauseNode extends PrimitiveArrayArgumentsNode {
374+
375+
@Specialization
376+
boolean isUsedAsACause(RubyException exception) {
377+
return exception.usedAsACause;
378+
}
379+
380+
@Fallback
381+
boolean isUsedAsACauseForeignException(Object exception) {
382+
return false;
383+
}
384+
385+
}
386+
387+
@Primitive(name = "exception_used_as_a_cause!")
388+
public abstract static class UsedAsACauseNode extends PrimitiveArrayArgumentsNode {
389+
390+
@Specialization
391+
Object usedAsACause(RubyException exception) {
392+
exception.usedAsACause = true;
393+
return nil;
394+
}
395+
396+
@Fallback
397+
Object usedAsACauseForeignException(Object exception) {
398+
return nil;
399+
}
400+
401+
}
402+
371403
}

src/main/java/org/truffleruby/core/exception/RubyException.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ public class RubyException extends RubyDynamicObject implements ObjectGraphNode
5050
public Object backtraceLocations = null;
5151
/** null (not set), RubyArray of Strings, or nil (empty) */
5252
public Object customBacktrace = null;
53+
public boolean usedAsACause = false;
5354

5455
public RubyException(RubyClass rubyClass, Shape shape, Object message, Backtrace backtrace, Object cause) {
5556
super(rubyClass, shape);

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

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -698,25 +698,29 @@ def raise(exc = undefined, msg = undefined, ctx = nil, cause: undefined, **kwarg
698698
msg = kwargs
699699
end
700700

701+
exc = Truffle::ExceptionOperations.build_exception_for_raise(exc, msg)
702+
exc.set_backtrace(ctx) if ctx
703+
Primitive.exception_capture_backtrace(exc, 1) unless Truffle::ExceptionOperations.backtrace?(exc)
704+
701705
if cause_given
702706
unless Primitive.is_a?(cause, ::Exception) || Primitive.nil?(cause)
703707
Truffle::ExceptionOperations.exception_object_expected!
704708
end
705709
else
706-
cause = $!
710+
if Primitive.nil?(exc.cause) && !Primitive.exception_used_as_a_cause?(exc)
711+
cause = $!
712+
else
713+
cause = nil
714+
end
707715
end
708716

709-
exc = Truffle::ExceptionOperations.build_exception_for_raise(exc, msg)
710-
711-
exc.set_backtrace(ctx) if ctx
712-
Primitive.exception_capture_backtrace(exc, 1) unless Truffle::ExceptionOperations.backtrace?(exc)
713-
714717
if !Primitive.nil?(cause) && (cause_given || Primitive.nil?(exc.cause)) && !Primitive.equal?(cause, exc)
715718
if Truffle::ExceptionOperations.circular_cause?(cause, exc)
716719
raise ArgumentError, 'circular causes'
717720
end
718721

719722
Primitive.exception_set_cause exc, cause
723+
Primitive.exception_used_as_a_cause!(cause)
720724
end
721725
end
722726

src/main/ruby/truffleruby/core/truffle/exception_operations.rb

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ def self.build_exception_for_raise(exc, msg)
2222

2323
exception_object_expected! unless Primitive.is_a?(exc, ::Exception)
2424
exc
25-
elsif Primitive.is_a?(exc, ::String)
25+
elsif Primitive.is_a?(exc, ::String) && Primitive.undefined?(msg)
2626
::RuntimeError.exception exc
2727
else
2828
exception_class_object_expected!
@@ -297,14 +297,14 @@ def self.append_causes(str, err, causes, reverse, highlight, options)
297297
append_causes(str, cause, causes, reverse, highlight, options)
298298
backtrace_message = backtrace_message(highlight, reverse, cause.backtrace, cause, options)
299299
if backtrace_message.empty?
300-
str << detailed_message_or_fallback(exception, options)
300+
str << detailed_message_or_fallback(cause, options)
301301
else
302302
str << backtrace_message
303303
end
304304
else
305305
backtrace_message = backtrace_message(highlight, reverse, cause.backtrace, cause, options)
306306
if backtrace_message.empty?
307-
str << detailed_message_or_fallback(exception, options)
307+
str << detailed_message_or_fallback(cause, options)
308308
else
309309
str << backtrace_message
310310
end

test/mri/excludes/TestException.rb

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
exclude :test_cause_raised_in_rescue, "cause should not be overwritten by reraise."
44
exclude :test_cause_thread_with_cause, "NoMethodError: undefined method `message' for nil:NilClass"
55
exclude :test_circular_cause, "Expected #<RuntimeError: error 2> to be nil."
6-
exclude :test_circular_cause_handle, "Exception(ArgumentError) with message matches to /circular cause/."
76
exclude :test_errinfo_encoding_in_debug, "Expected \"\" to include \"#<Module:0x608>::Cエラー\"."
87
exclude :test_errinfo_in_debug, "needs investigation"
98
exclude :test_exception_in_ensure_with_next, "NameError: uninitialized constant TestException::RubyVM"
@@ -13,7 +12,6 @@
1312
exclude :test_marshal_circular_cause, "\"\\x04\\bo:\\x11RuntimeError\\b:\\tmesgI\\\"\\berr\\x06:\\x06ET:\\abt[\\x00:\\ncauseo:\\x0EException\\a;\\x060;\\b0\"."
1413
exclude :test_non_exception_cause, "Exception(TypeError) with message matches to /exception/."
1514
exclude :test_output_string_encoding, "<1> expected but was <0>."
16-
exclude :test_raise_with_cause, "[TypeError] exception expected, not #<RuntimeError: [Feature #8257]>."
1715
exclude :test_stackoverflow, "<no message> (java.lang.IndexOutOfBoundsException) from com.oracle.svm.core.jni.functions.JNIFunctions$NewObjectWithObjectArrayArgFunctionPointer.invoke(JNIFunctions.java:-1)"
1816
exclude :test_too_many_args_in_eval, "[SystemStackError] exception expected, not #<ArgumentError: wrong number of arguments (given 140000, expected 0..3)>."
1917
exclude :test_undef_Warning_warn, "[NoMethodError] exception expected, not #<NameError: undefined method `warn' for class `Module'>."

0 commit comments

Comments
 (0)