[DOC] Adjust documentation related to backtraces (#12420)

This commit is contained in:
Victor Shepelev 2024-12-24 20:49:55 +02:00 committed by GitHub
parent 3be1baab82
commit 58460b4dbd
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
Notes: git 2024-12-24 18:50:15 +00:00
Merged-By: zverok <zverok.offline@gmail.com>
3 changed files with 194 additions and 73 deletions

View File

@ -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.

187
error.c
View File

@ -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+.
*
* x = RuntimeError.new('Boom')
* x.set_backtrace(%w[foo bar baz]) # => ["foo", "bar", "baz"]
* x.backtrace # => ["foo", "bar", "baz"]
* The +value+ might be:
*
* The given +value+ must be an array of strings, a single string, or +nil+.
* - an array of Thread::Backtrace::Location;
* - an array of String instances;
* - a single String instance; or
* - +nil+.
*
* Does not affect the value returned by #backtrace_locations.
* 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"')
*
* Calling #set_backtrace with +nil+ clears up #backtrace but doesn't affect
* #backtrace_locations:
*
* 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"')
*
* 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].
*/

35
eval.c
View File

@ -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,