Skip to content

Commit a5654fc

Browse files
authored
Add docs for ABI Backtracing (#7435)
## Description This PR: - extends the docs on Irrecoverable Errors by explaining the ABI Backtracing. - adds docs for the `#[trace]` attribute. - adds docs for the `backtrace` build option. It is the final step in implementing the #7276. ## Checklist - [x] I have linked to any relevant issues. - [ ] I have commented my code, particularly in hard-to-understand areas. - [x] I have updated the documentation where relevant (API docs, the reference, and the Sway book). - [ ] If my change requires substantial documentation changes, I have [requested support from the DevRel team](https://github.com/FuelLabs/devrel-requests/issues/new/choose) - [ ] I have added tests that prove my fix is effective or that my feature works. - [ ] I have added (or requested a maintainer to add) the necessary `Breaking*` or `New Feature` labels where relevant. - [x] I have done my best to ensure that my PR adheres to [the Fuel Labs Code Review Standards](https://github.com/FuelLabs/rfcs/blob/master/text/code-standards/external-contributors.md). - [x] I have requested a review from the relevant team or maintainers.
1 parent 107b092 commit a5654fc

File tree

4 files changed

+187
-36
lines changed

4 files changed

+187
-36
lines changed

docs/book/spell-check-custom-words.txt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -266,3 +266,7 @@ CallResponse
266266
md
267267
URIs
268268
Const
269+
backtrace
270+
Backtrace
271+
backtracing
272+
Backtracing

docs/book/src/basics/error_handling.md

Lines changed: 166 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -20,44 +20,38 @@ if some_error_occurred {
2020
}
2121
```
2222

23-
At runtime, the `panic` expression aborts and reverts the execution of the entire program. At compile time, for each `panic` encountered in code, Sway compiler will generate a unique revert code and create an entry in the ABI JSON `errorCodes` section. The generated `errorCodes` entry will contain the information about source location at which the `panic` occurs, as well as the error message.
23+
At runtime, the `panic` expression aborts and reverts the execution of the entire program. At compile time, for each `panic` encountered in code, Sway compiler will generate a unique revert code and create an entry in the ABI JSON `errorCodes` section. The generated `errorCodes` entry will contain the information about the source location at which the `panic` occurs, as well as the error message.
2424

25-
**This mechanism allows for getting a rich troubleshooting information, without an additional on-chain cost.** The generated bytecode will contain only the revert instruction, and the remaining information, the error message and the error location, are stored off-chain, in the ABI JSON file.
25+
In addition, for each function call that might panic, compiler will generate an entry in the ABI JSON `panickingCalls` section. Those entries will contain source locations of functions whose calls might eventually end up in calling a `panic` somewhere in the call chain.
2626

27-
For example, let's assume that the above code is situated in the module `some_module`, contained within the version `v1.2.3` of the package `some_package`.
27+
**Combined together, these two ABI JSON entries, `errorCodes` and `panickingCalls`, allow for getting a rich troubleshooting information, that contains the error location and a partial backtrace, without any or negligible additional on-chain cost.**
2828

29-
At runtime, the `panic` will result in a compiler generated revert code, e.g., 18446744069414584323. At compile time, an entry similar to this will be added to the ABI JSON `errorCodes` section:
29+
The generated bytecode will contain only the revert instructions, while error messages and error locations will be stored off-chain, in the ABI JSON file, making the `panic`king a zero on-chain cost operation. Panic backtrace comes with only a negligible on-chain cost, that can additionally be opted-in or out, as explained in detail in the chapter [Configuring the Backtrace Content](#configuring-the-backtrace-content).
3030

31-
```json
32-
"errorCodes": {
33-
"18446744069414584323": {
34-
"pos": {
35-
36-
"file": "some_module.sw",
37-
"line": 13,
38-
"column": 9
39-
},
40-
"logId": null,
41-
"msg": "Some error has occurred."
42-
},
43-
}
44-
```
31+
_Partial_ backtrace means that the _backtrace is limited to up to five function calls_. In practice, deeper call chains are rare. Also, it is possible to choose which functions should be a part of the reported backtrace. This is done by using the `backtrace` build option and the `#[trace]` attribute, as also explained in detail in the chapter [Configuring the Backtrace Content](#configuring-the-backtrace-content).
4532

46-
Rust and TypeScript SDK, as well as `forc test`, recognize revert codes generated from `panic` expressions. E.g., if a Sway unit test fails because of a revert caused by the above `panic` line, the `forc test` will display the following:
33+
Tools like Rust and TypeScript SDKs and `forc test` recognize revert codes generated by `panic` expressions. E.g., if a Sway unit test fails because of a revert caused by the above `panic` line, `forc test` might display an output similar to the following:
4734

4835
```console
49-
test some_test, "path/to/failing/test.sw":42
50-
revert code: ffffffff00000003
36+
test some_test, "/tests.sw":42
37+
revert code: 8100000000000000
5138
├─ panic message: Some error has occurred.
52-
└─ panicked in: [email protected], src/some_module.sw:13:9
39+
├─ panicked: in some_package::some_error_occurred
40+
│ └─ at [email protected], src/some_module.sw:13:9
41+
└─ backtrace: called in some_other_package::some_other_module::some_function
42+
└─ at [email protected], src/some_other_module.sw:106:11
43+
called in my_project::some_test
44+
└─ at my_project, src/tests.sw:48:8
5345
```
5446

47+
What the above panic location and the backtrace are telling us, is that `some_test` has called `some_function` that has called `some_error_occurred` which has panicked with the message "Some error has occurred."
48+
5549
### Error Types
5650

57-
Passing textual error messages directly as a `panic` argument is the most convenient way to provide a helpful error message. It is sufficient for many use-cases. However, often we want:
51+
Passing textual error messages directly as a `panic` argument is the most convenient way to provide a helpful error message. It is sufficient for many use-cases. However, sometimes we want:
5852

59-
- to provide an additional runtime information about the error.
60-
- group a certain family of errors together.
53+
- to provide an additional runtime information about the error,
54+
- or to group a certain family of errors together.
6155

6256
For these use-cases, you can use _error types_. Error types are enums annotated with the `#[error_type]` attribute, whose all variants are attributed with the `#[error(m = "<error message>")]` attributes. Each variant represent a particular error, and the enum itself the family of errors. The convention is to postfix the names of error type enums with `Error`.
6357

@@ -84,17 +78,158 @@ fn do_something_that_requires_admin_access(admin: Identity) {
8478
if !is_admin(admin) {
8579
panic AccessRightError::NotAnAdmin(admin);
8680
}
87-
8881
// ...
8982
}
9083
```
9184

92-
Assuming we have a failing test for the above function, the test output will show the error message, but also the provided `Identity`. E.g.:
85+
Assuming we have a failing test for the above function, the test output will show the error message, but also the provided `Identity` as the _panic value_. E.g.:
86+
87+
```console
88+
test some_test_for_admin_access, "/test.sw":42
89+
revert code: 8100000000000000
90+
├─ panic message: The provided identity is not an administrator.
91+
├─ panic value: NotAnAdmin(Address(Address(79fa8779bed2f36c3581d01c79df8da45eee09fac1fd76a5a656e16326317ef0)))
92+
├─ panicked: in auth_package::only_admin
93+
│ └─ at [email protected], src/admin_access.sw:11:9
94+
└─ backtrace: ...
95+
```
96+
97+
### `errorCodes` and `panickingCalls` ABI JSON Entries
98+
99+
To explain how `errorCodes` and `panickingCalls` are used to provide error information and backtrace, let's consider the following example:
100+
101+
- a function `only_admin` is defined in the `auth_package` and it panics if the identity is not an admin.
102+
- `only_admin` is called in various guard functions that check preconditions, defined in the `guards_package`. E.g., `check_access_rights`.
103+
- those `guards_package` functions are used within the `funds_contract`.
104+
105+
At compile time, an entry similar to these will be added to the ABI JSON `errorCodes` and `panickingCalls` sections:
106+
107+
```json
108+
"errorCodes": {
109+
"0": { // Unique ID of a `panic` call.
110+
"pos": { // Location in code, at which the `panic` call occurs.
111+
112+
"function": "auth_package::admin_access::only_admin",
113+
"file": "src/admin_access.sw",
114+
"line": 13,
115+
"column": 9
116+
},
117+
"logId": "10098701174489624218", // Log ID representing the `AccessRightError` enum
118+
// passed as an argument to `panic`.
119+
"msg": null,
120+
},
121+
// Other error codes for other `panic` calls.
122+
},
123+
124+
"panickingCalls": {
125+
"1": { // Unique ID of a potentially panicking function call.
126+
"pos": { // Location in code, at which the function call that might panic occurs.
127+
"function": "guards_package::preconditions::check_admin", // The caller function, `check_admin`.
128+
129+
// Position within the `check_admin` where `only_admin` is called.
130+
"file": "src/preconditions.sw",
131+
"line": 4,
132+
"column": 9
133+
},
134+
"function": "auth_package::admin_access::only_admin" // The called function, `only_admin`.
135+
},
136+
// Other panicking calls.
137+
}
138+
```
139+
140+
Those unique error and panicking call IDs are embedded by the compiler into the revert code generated for a particular `panic` call.
141+
142+
In case of a revert, tools like SDKs and `forc test` will extract those IDs from the received revert code and using the information contained in the ABI JSON provide a rich troubleshooting details. In the above example, in case of a failing test, `forc test` might display an output similar to the following:
93143

94144
```console
95-
test some_test_for_admin_access, "path/to/failing/test.sw":42
96-
revert code: ffffffff00000007
145+
test some_test, "/tests.sw":42
146+
revert code: 8280000000000003
97147
├─ panic message: The provided identity is not an administrator.
98-
├─ panic value: NotAnAdmin(Address(Address()))
99-
└─ panicked in: [email protected], src/admin_module.sw:11:9
148+
├─ panic value: NotAnAdmin(Address(Address(79fa8779bed2f36c3581d01c79df8da45eee09fac1fd76a5a656e16326317ef0)))
149+
├─ panicked: in auth_package::admin_access::only_admin
150+
│ └─ at [email protected], src/admin_access.sw:13:9
151+
└─ backtrace: called in guards_package::preconditions::check_admin
152+
└─ at [email protected], src/preconditions.sw:4:9
153+
called in guards_package::preconditions::check_access_rights
154+
└─ at [email protected], src/preconditions.sw:23:13
155+
called in guards_package::preconditions::check_preconditions
156+
└─ at [email protected], src/preconditions.sw:57:9
157+
called in <Contract as Funds>::transfer_funds
158+
└─ at [email protected], src/main.sw:22:9
159+
```
160+
161+
## Configuring the Backtrace Content
162+
163+
### In Default Builds
164+
165+
Backtracing comes with a minimal on-chain cost, in terms of the bytecode size and gas usage. To additionally allow you to opt-in even for this minimal cost, backtracing is configurable via dedicated `backtrace` build option, with different default values for `debug` and `release` builds.
166+
167+
To explain this build option, let us use the following example. We will have five functions named `first`, `second`, ..., `fifth` that call each other sequentially, and the function `fifth` finally calling a failing `assert_eq` that `panic`s.
168+
169+
In the default `debug` build, the output of a failing `forc test` will look similar to this (package names and code locations are omitted for brevity):
170+
171+
```console
172+
test some_test, "test.sw":42
173+
revert code: 8280000000000003
174+
├─ panic message: The provided `expected` and `actual` values are not equal.
175+
├─ panic value: AssertEq(AssertEq { expected: 42, actual: 43 })
176+
├─ panicked: in std::assert::assert_eq
177+
│ └─ at [email protected], src/assert.sw:80:9
178+
└─ backtrace: called in fifth
179+
└─ at ...
180+
called in fourth
181+
└─ at ...
182+
called in third
183+
└─ at ...
184+
called in second
185+
└─ at ...
186+
called in first
187+
└─ at ...
188+
```
189+
190+
In the default `release` build, the backtrace output will contain only the immediate call of the `assert_eq` that happens in the `fifth`:
191+
192+
```console
193+
...
194+
├─ panicked: in std::assert::assert_eq
195+
│ └─ at [email protected], src/assert.sw:80:9
196+
└─ backtrace: called in fifth
197+
└─ at ...
198+
```
199+
200+
Where this difference in backtrace in `debug` and `release` build is coming from? In other words, how the compiler knows which functions to include into backtrace in different build profiles?
201+
202+
The backtrace can be directly influenced by using the `#[trace]` attribute in your code. Similarly to the `#[inline]` attribute, the `#[trace]` attribute can be used on all functions that have implementations. Same like `#[inline]`, it also comes with two arguments, `always` and `never`.
203+
204+
The `#[trace(always)]` instructs the compiler to include the calls of annotated functions in the backtrace in default `release` builds. This attribute should be used to annotate guard functions, like, e.g., `assert`, `assert_eq`, `require`, and `only_owner`, or methods like `Option::unwrap`. When such functions panic, we are actually interested in the places in code in which a failing call happens. E.g., having `#[trace(always)]` on the `assert` function helps us to see _which actual `assert` call has failed_.
205+
206+
**By default, `release` builds will include in the backtrace only the calls to functions annotated with `#[trace(always)]`.** This minimizes the anyhow low on-chain cost of backtrace calculation only to function calls of those function, giving almost a zero on-chain impact while still providing a valuable troubleshooting information.
207+
208+
By using `#[trace(never)]` you can instruct the compiler _not to include a function in a backtrace_, even not in a default `debug` build, which otherwise includes all the panicking calls into backtrace. This is useful, considering that the backtrace will be limited to five functions only. If an intermediate function call can be easily deducted, it might be worth not having it in the backtrace.
209+
210+
E.g., let's assume that the functions `fourth` and `second` are annotated with `#[trace(never)]`. The default `debug` build would then output the following backtrace:
211+
212+
```console
213+
...
214+
├─ panicked: in std::assert::assert_eq
215+
│ └─ at [email protected], src/assert.sw:80:9
216+
└─ backtrace: called in fifth
217+
└─ at ...
218+
called in third
219+
└─ at ...
220+
called in first
221+
└─ at ...
100222
```
223+
224+
### In Custom Builds
225+
226+
To change this default behavior, use the `backtrace` build option. The possible values for the `backtrace` build option, and their meanings are given in the below table.
227+
228+
| Value | Meaning |
229+
| ----- | ------- |
230+
| all | Backtrace all function calls, even of functions annotated with `#[trace(never)]`. |
231+
| all_except_never | Backtrace all function calls, except those of functions annotated with `#[trace(never)]`. This is the default value for `debug` builds. |
232+
| only_always | Backtrace only calls of functions annotated with `#[trace(always)]`. This is the default value for `release` builds. |
233+
| none | Do not backtrace any function calls. Use this option only if you need to fully remove the on-chain cost of backtracing. Considering how negligible the cost is, this will very likely never be needed. |
234+
235+
To learn more about custom builds, see [The `[build-profile.*]` Section](../forc/manifest_reference.md#the-build-profile-section).

docs/book/src/forc/manifest_reference.md

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ The `Forc.toml` (the _manifest_ file) is a compulsory file for each package and
3131

3232
* [`[contract-dependencies]`](#the-contract-dependencies-section) - Defines the contract dependencies.
3333

34-
## The `[project]` section
34+
## The `[project]` Section
3535

3636
An example `Forc.toml` is shown below. Under `[project]` the following fields are optional:
3737

@@ -145,7 +145,7 @@ This allows for a streamlined developer experience while maintaining clear separ
145145
* [forc-index-ts](https://github.com/FuelLabs/example-forc-plugins/tree/master/forc-index-ts): A TypeScript CLI tool for parsing `Forc.toml` metadata to read contract ABI JSON file.
146146
* [forc-index-rs](https://github.com/FuelLabs/example-forc-plugins/tree/master/forc-index-rs): A Rust CLI tool for parsing `Forc.toml` metadata to read contract ABI JSON file.
147147

148-
## The `[dependencies]` section
148+
## The `[dependencies]` Section
149149

150150
The following fields can be provided with a dependency:
151151

@@ -159,13 +159,13 @@ The following fields can be provided with a dependency:
159159

160160
Please see [dependencies](./dependencies.md) for details
161161

162-
## The `[network]` section
162+
## The `[network]` Section
163163

164164
For the following fields, a default value is provided so omitting them is allowed:
165165

166166
* `URL` - (default: _<http://127.0.0.1:4000>_)
167167

168-
## The `[build-profile.*]` section
168+
## The `[build-profile.*]` Section
169169

170170
The `[build-profile]` tables provide a way to customize compiler settings such as debug options.
171171

@@ -180,6 +180,7 @@ The following fields can be provided for a build-profile:
180180
* `time_phases` - Whether to output the time elapsed over each part of the compilation process, defaults to false.
181181
* `include_tests` - Whether or not to include test functions in parsing, type-checking, and code generation. This is set to true by invocations like `forc test`, but defaults to false.
182182
* `error_on_warnings` - Whether to treat errors as warnings, defaults to false.
183+
* `backtrace` - Defines which panicking functions to include in a `panic` backtrace. Possible values are `all`, `all_except_never`, `only_always`, and `none`. Defaults to `all_except_never` and `only_always` in the default `debug` and `release` profiles, respectively. For more information on backtracing see the chapter [Irrecoverable Errors](../basics/error_handling.md#irrecoverable-errors).
183184

184185
There are two default `[build-profile]` available with every manifest file. These are `debug` and `release` profiles. If you want to override these profiles, you can provide them explicitly in the manifest file like the following example:
185186

@@ -217,7 +218,7 @@ error-on-warnings = false
217218
experimental-private-modules = false
218219
```
219220

220-
## The `[patch]` section
221+
## The `[patch]` Section
221222

222223
The [patch] section of `Forc.toml` can be used to override dependencies with other copies. The example provided below patches `https://github.com/fuellabs/sway` with the `test` branch of the same repo.
223224

docs/book/src/reference/attributes.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ Below is the list of attributes supported by the Sway compiler, ordered alphabet
1515
- [Payable](#payable)
1616
- [Storage](#payable)
1717
- [Test](#test)
18+
- [Tracing](#tracing)
1819

1920
## ABI Name
2021

@@ -155,3 +156,13 @@ The `#[test]` attribute marks a function to be executed as a test.
155156
The `#[test(should_revert)]` attribute marks a function to be executed as a test that should revert.
156157

157158
More details in [Unit Testing](../testing/unit-testing.md).
159+
160+
## Tracing
161+
162+
The tracing attribute tells the compiler if a function should be included in a backtrace of a revert caused by a `panic` expression call.
163+
164+
The `#[tracing(never)]` signals the compiler not to include the function in a backtrace, unless the `backtrace` build option is set to `all`.
165+
166+
The `#[tracing(always)]` signals the compiler to always include the function in a backtrace, unless the `backtrace` build option is set to `none`.
167+
168+
More details in [Irrecoverable Errors](../basics/error_handling.md#irrecoverable-errors).

0 commit comments

Comments
 (0)