summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--doc/exceptions.md45
-rw-r--r--error.c187
-rw-r--r--eval.c35
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.
diff --git a/error.c b/error.c
index d69a342a9b..020ef1f165 100644
--- a/error.c
+++ b/error.c
@@ -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].
*/
diff --git a/eval.c b/eval.c
index 6224e54ef7..4481c47def 100644
--- a/eval.c
+++ b/eval.c
@@ -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,