diff options
-rw-r--r-- | doc/exceptions.md | 45 | ||||
-rw-r--r-- | error.c | 187 | ||||
-rw-r--r-- | eval.c | 35 |
3 files changed, 194 insertions, 73 deletions
diff --git a/doc/exceptions.md b/doc/exceptions.md index 38bb618b75..92e3ab6fd3 100644 --- a/doc/exceptions.md +++ b/doc/exceptions.md @@ -26,7 +26,7 @@ that prints a message and exits the program (or thread): ```console $ ruby -e "raise" --e:1:in `<main>': unhandled exception +-e:1:in '<main>': unhandled exception ``` ### Rescued Exceptions @@ -201,7 +201,7 @@ Output: ``` #<ZeroDivisionError: divided by 0> -["t.rb:2:in `/'", "t.rb:2:in `<main>'"] +["t.rb:2:in 'Integer#/'", "t.rb:2:in '<main>'"] ``` ##### Cause @@ -362,8 +362,8 @@ Output: ``` ruby t.rb -t.rb:2:in `/': divided by 0 (ZeroDivisionError) - from t.rb:2:in `<main>' +t.rb:2:in 'Integer#/': divided by 0 (ZeroDivisionError) + from t.rb:2:in '<main>' ``` #### Retrying @@ -501,28 +501,21 @@ These methods return backtrace information: of Thread::Backtrace::Location objects or `nil`. Each Thread::Backtrace::Location object gives detailed information about a called method. -An `Exception` object stores its backtrace value as one of: +By default, Ruby sets the backtrace of the exception to the location where it +was raised. -- An array of Thread::Backtrace::Location objects; - this is the common case: the exception was raised by the Ruby core or the Ruby standard library. - In this case: +The developer might adjust this by either providing +backtrace+ argument +to Kernel#raise, or using Exception#set_backtrace. - - Exception#backtrace_locations returns the array of Thread::Backtrace::Location objects. - - Exception#backtrace returns the array of their string values - (`Exception#backtrace_locations.map {|loc| loc.to_s }`). - -- An array of strings; - this is an uncommon case: the user manually set the backtrace to an array of strings; - In this case: - - - Exception#backtrace returns the array of strings. - - Exception#backtrace_locations returns `nil`. - -- `nil`, in which case both methods return `nil`. - -These methods set the backtrace value: - -- Exception#set_backtrace: sets the backtrace value to an array of strings, or to `nil`. -- Kernel#raise: sets the backtrace value to an array of Thread::Backtrace::Location objects, - or to an array of strings. +Note that: +- by default, both +backtrace+ and +backtrace_locations+ represent the same backtrace; +- if the developer sets the backtrace by one of the above methods to an array of + Thread::Backtrace::Location, they still represent the same backtrace; +- if the developer sets the backtrace to a string or an array of strings: + - by Kernel#raise: +backtrace_locations+ become +nil+; + - by Exception#set_backtrace: +backtrace_locations+ preserve the original + value; +- if the developer sets the backtrace to +nil+ by Exception#set_backtrace, + +backtrace_locations+ preserve the original value; but if the exception is then + reraised, both +backtrace+ and +backtrace_locations+ become the location of reraise. @@ -1706,16 +1706,16 @@ check_order_keyword(VALUE opt) * Output: * * "divided by 0" - * ["t.rb:3:in `/': divided by 0 (ZeroDivisionError)", - * "\tfrom t.rb:3:in `baz'", - * "\tfrom t.rb:10:in `bar'", - * "\tfrom t.rb:11:in `foo'", - * "\tfrom t.rb:12:in `<main>'"] - * ["t.rb:3:in `/': \e[1mdivided by 0 (\e[1;4mZeroDivisionError\e[m\e[1m)\e[m", - * "\tfrom t.rb:3:in `baz'", - * "\tfrom t.rb:10:in `bar'", - * "\tfrom t.rb:11:in `foo'", - * "\tfrom t.rb:12:in `<main>'"] + * ["t.rb:3:in 'Integer#/': divided by 0 (ZeroDivisionError)", + * "\tfrom t.rb:3:in 'Object#baz'", + * "\tfrom t.rb:10:in 'Object#bar'", + * "\tfrom t.rb:11:in 'Object#foo'", + * "\tfrom t.rb:12:in '<main>'"] + * ["t.rb:3:in 'Integer#/': \e[1mdivided by 0 (\e[1;4mZeroDivisionError\e[m\e[1m)\e[m", + * "\tfrom t.rb:3:in 'Object#baz'", + * "\tfrom t.rb:10:in 'Object#bar'", + * "\tfrom t.rb:11:in 'Object#foo'", + * "\tfrom t.rb:12:in '<main>'"] * * An overriding method should be careful with ANSI code enhancements; * see {Messages}[rdoc-ref:exceptions.md@Messages]. @@ -1864,26 +1864,31 @@ exc_inspect(VALUE exc) * call-seq: * backtrace -> array or nil * - * Returns a backtrace value for +self+; - * the returned value depends on the form of the stored backtrace value: + * Returns the backtrace (the list of code locations that led to the exception), + * as an array of strings. * - * - \Array of Thread::Backtrace::Location objects: - * returns the array of strings given by - * <tt>Exception#backtrace_locations.map {|loc| loc.to_s }</tt>. - * This is the normal case, where the backtrace value was stored by Kernel#raise. - * - \Array of strings: returns that array. - * This is the unusual case, where the backtrace value was explicitly - * stored as an array of strings. - * - +nil+: returns +nil+. + * Example (assuming the code is stored in the file named <tt>t.rb</tt>): * - * Example: + * def division(numerator, denominator) + * numerator / denominator + * end * * begin - * 1 / 0 - * rescue => x - * x.backtrace.take(2) + * division(1, 0) + * rescue => ex + * p ex.backtrace + * # ["t.rb:2:in 'Integer#/'", "t.rb:2:in 'Object#division'", "t.rb:6:in '<main>'"] + * loc = ex.backtrace.first + * p loc.class + * # String * end - * # => ["(irb):132:in `/'", "(irb):132:in `<top (required)>'"] + * + * The value returned by this method migth be adjusted when raising (see Kernel#raise), + * or during intermediate handling by #set_backtrace. + * + * See also #backtrace_locations that provide the same value, as structured objects. + * (Note though that two values might not be consistent with each other when + * backtraces are manually adjusted.) * * see {Backtraces}[rdoc-ref:exceptions.md@Backtraces]. */ @@ -1930,20 +1935,37 @@ rb_get_backtrace(VALUE exc) * call-seq: * backtrace_locations -> array or nil * - * Returns a backtrace value for +self+; - * the returned value depends on the form of the stored backtrace value: + * Returns the backtrace (the list of code locations that led to the exception), + * as an array of Thread::Backtrace::Location instances. * - * - \Array of Thread::Backtrace::Location objects: returns that array. - * - \Array of strings or +nil+: returns +nil+. + * Example (assuming the code is stored in the file named <tt>t.rb</tt>): * - * Example: + * def division(numerator, denominator) + * numerator / denominator + * end * * begin - * 1 / 0 - * rescue => x - * x.backtrace_locations.take(2) + * division(1, 0) + * rescue => ex + * p ex.backtrace_locations + * # ["t.rb:2:in 'Integer#/'", "t.rb:2:in 'Object#division'", "t.rb:6:in '<main>'"] + * loc = ex.backtrace_locations.first + * p loc.class + * # Thread::Backtrace::Location + * p loc.path + * # "t.rb" + * p loc.lineno + * # 2 + * p loc.label + * # "Integer#/" * end - * # => ["(irb):150:in `/'", "(irb):150:in `<top (required)>'"] + * + * The value returned by this method might be adjusted when raising (see Kernel#raise), + * or during intermediate handling by #set_backtrace. + * + * See also #backtrace that provide the same value as an array of strings. + * (Note though that two values might not be consistent with each other when + * backtraces are manually adjusted.) * * See {Backtraces}[rdoc-ref:exceptions.md@Backtraces]. */ @@ -1985,15 +2007,100 @@ rb_check_backtrace(VALUE bt) * call-seq: * set_backtrace(value) -> value * - * Sets the backtrace value for +self+; returns the given +value: + * Sets the backtrace value for +self+; returns the given +value+. + * + * The +value+ might be: + * + * - an array of Thread::Backtrace::Location; + * - an array of String instances; + * - a single String instance; or + * - +nil+. + * + * Using array of Thread::Backtrace::Location is the most consistent + * option: it sets both #backtrace and #backtrace_locations. It should be + * preferred when possible. The suitable array of locations can be obtained + * from Kernel#caller_locations, copied from another error, or just set to + * the adjusted result of the current error's #backtrace_locations: + * + * require 'json' + * + * def parse_payload(text) + * JSON.parse(text) # test.rb, line 4 + * rescue JSON::ParserError => ex + * ex.set_backtrace(ex.backtrace_locations[2...]) + * raise + * end + * + * parse_payload('{"wrong: "json"') + * # test.rb:4:in 'Object#parse_payload': unexpected token at '{"wrong: "json"' (JSON::ParserError) + * # + * # An error points to the body of parse_payload method, + * # hiding the parts of the backtrace related to the internals + * # of the "json" library + * + * # The error has both #backtace and #backtrace_locations set + * # consistently: + * begin + * parse_payload('{"wrong: "json"') + * rescue => ex + * p ex.backtrace + * # ["test.rb:4:in 'Object#parse_payload'", "test.rb:20:in '<main>'"] + * p ex.backtrace_locations + * # ["test.rb:4:in 'Object#parse_payload'", "test.rb:20:in '<main>'"] + * end + * + * When the desired stack of locations is not available and should + * be constructed from scratch, an array of strings or a singular + * string can be used. In this case, only #backtrace is affected: + * + * def parse_payload(text) + * JSON.parse(text) + * rescue JSON::ParserError => ex + * ex.set_backtrace(["dsl.rb:34", "framework.rb:1"]) + * # The error have the new value in #backtrace: + * p ex.backtrace + * # ["dsl.rb:34", "framework.rb:1"] + * + * # but the original one in #backtrace_locations + * p ex.backtrace_locations + * # [".../json/common.rb:221:in 'JSON::Ext::Parser.parse'", ...] + * end + * + * parse_payload('{"wrong: "json"') * - * x = RuntimeError.new('Boom') - * x.set_backtrace(%w[foo bar baz]) # => ["foo", "bar", "baz"] - * x.backtrace # => ["foo", "bar", "baz"] + * Calling #set_backtrace with +nil+ clears up #backtrace but doesn't affect + * #backtrace_locations: * - * The given +value+ must be an array of strings, a single string, or +nil+. + * def parse_payload(text) + * JSON.parse(text) + * rescue JSON::ParserError => ex + * ex.set_backtrace(nil) + * p ex.backtrace + * # nil + * p ex.backtrace_locations + * # [".../json/common.rb:221:in 'JSON::Ext::Parser.parse'", ...] + * end + * + * parse_payload('{"wrong: "json"') * - * Does not affect the value returned by #backtrace_locations. + * On reraising of such an exception, both #backtrace and #backtrace_locations + * is set to the place of reraising: + * + * def parse_payload(text) + * JSON.parse(text) + * rescue JSON::ParserError => ex + * ex.set_backtrace(nil) + * raise # test.rb, line 7 + * end + * + * begin + * parse_payload('{"wrong: "json"') + * rescue => ex + * p ex.backtrace + * # ["test.rb:7:in 'Object#parse_payload'", "test.rb:11:in '<main>'"] + * p ex.backtrace_locations + * # ["test.rb:7:in 'Object#parse_payload'", "test.rb:11:in '<main>'"] + * end * * See {Backtraces}[rdoc-ref:exceptions.md@Backtraces]. */ @@ -782,16 +782,37 @@ rb_f_raise(int argc, VALUE *argv) * * See {Messages}[rdoc-ref:exceptions.md@Messages]. * - * Argument +backtrace+ sets the stored backtrace in the new exception, - * which may be retrieved by method Exception#backtrace; - * the backtrace must be an array of strings or +nil+: + * Argument +backtrace+ might be used to modify the backtrace of the new exception, + * as reported by Exception#backtrace and Exception#backtrace_locations; + * the backtrace must be an array of Thread::Backtrace::Location, an array of + * strings, a single string, or +nil+. + * + * Using the array of Thread::Backtrace::Location instances is the most consistent option + * and should be preferred when possible. The necessary value might be obtained + * from #caller_locations, or copied from Exception#backtrace_locations of another + * error: * * begin - * raise(StandardError, 'Boom', %w[foo bar baz]) - * rescue => x - * p x.backtrace + * do_some_work() + * rescue ZeroDivisionError => ex + * raise(LogicalError, "You have an error in your math", ex.backtrace_locations) + * end + * + * The ways, both Exception#backtrace and Exception#backtrace_locations of the + * raised error are set to the same backtrace. + * + * When the desired stack of locations is not available and should + * be constructed from scratch, an array of strings or a singular + * string can be used. In this case, only Exception#backtrace is set: + * + * begin + * raise(StandardError, 'Boom', %w[dsl.rb:3 framework.rb:1]) + * rescue => ex + * p ex.backtrace + * # => ["dsl.rb:3", "framework.rb:1"] + * p ex.backtrace_locations + * # => nil * end - * # => ["foo", "bar", "baz"] * * If argument +backtrace+ is not given, * the backtrace is set according to an array of Thread::Backtrace::Location objects, |