@@ -13,6 +13,39 @@ pub trait Source: core::error::Error + Send + Sync + 'static {}
1313
1414impl < T > Source for T where T : core:: error:: Error + Send + Sync + ' static { }
1515
16+ /// Optional positional metadata attached to an [`Error`] when the failure
17+ /// occurred at a known byte offset within a wire-format input stream, and/or
18+ /// while processing a known symbolic field.
19+ ///
20+ /// Populated via [`Error::at_offset`] and [`Error::in_field`]. These builders
21+ /// compose: both may be set independently on the same error. When either is
22+ /// present, the position is rendered in [`Error`]'s `Display` output.
23+ ///
24+ /// Intended primarily for decode and encode errors where the byte offset and
25+ /// the field being processed materially aid triage, particularly for
26+ /// fuzz-crash analysis where the variant kind alone does not narrow the
27+ /// failure point down enough.
28+ ///
29+ /// Available only when the `alloc` feature is enabled, since it lives in
30+ /// [`Error`]'s heap-allocated metadata.
31+ #[ derive( Clone , Copy , Debug , Default , PartialEq , Eq ) ]
32+ #[ non_exhaustive]
33+ pub struct ErrorPosition {
34+ /// Byte offset in the input stream where the error was detected.
35+ pub offset : Option < usize > ,
36+ /// Symbolic name of the field or sub-PDU being processed when the error
37+ /// occurred. Dotted notation may be used for nested PDU navigation,
38+ /// e.g. `"ServerSecurityData.serverRandom"`.
39+ pub field : Option < & ' static str > ,
40+ }
41+
42+ impl ErrorPosition {
43+ /// Returns `true` if at least one of `offset` or `field` is set.
44+ pub const fn is_set ( & self ) -> bool {
45+ self . offset . is_some ( ) || self . field . is_some ( )
46+ }
47+ }
48+
1649/// Diagnostic metadata stored behind a [`Box`] so that `Error<Kind>` stays small.
1750///
1851/// All fields here are purely for display and error-chain traversal; none are
@@ -24,6 +57,7 @@ struct ErrorMeta {
2457 context : & ' static str ,
2558 location : & ' static core:: panic:: Location < ' static > ,
2659 source : Option < Box < dyn Source > > ,
60+ position : Option < ErrorPosition > ,
2761}
2862
2963/// A typed error wrapper carrying a `Kind` discriminant plus diagnostic metadata.
@@ -53,9 +87,14 @@ impl<Kind: fmt::Debug> fmt::Debug for Error<Kind> {
5387 fn fmt ( & self , f : & mut fmt:: Formatter < ' _ > ) -> fmt:: Result {
5488 let mut dbg = f. debug_struct ( "Error" ) ;
5589 #[ cfg( feature = "alloc" ) ]
56- dbg. field ( "context" , & self . meta . context )
57- . field ( "kind" , & self . kind )
58- . field ( "source" , & self . meta . source ) ;
90+ {
91+ dbg. field ( "context" , & self . meta . context )
92+ . field ( "kind" , & self . kind )
93+ . field ( "source" , & self . meta . source ) ;
94+ if let Some ( position) = & self . meta . position {
95+ dbg. field ( "position" , position) ;
96+ }
97+ }
5998 #[ cfg( not( feature = "alloc" ) ) ]
6099 dbg. field ( "context" , & self . context ) . field ( "kind" , & self . kind ) ;
61100 dbg. finish ( )
@@ -74,6 +113,7 @@ impl<Kind> Error<Kind> {
74113 context,
75114 location : core:: panic:: Location :: caller ( ) ,
76115 source : None ,
116+ position : None ,
77117 } ) ,
78118 #[ cfg( not( feature = "alloc" ) ) ]
79119 context,
@@ -130,6 +170,60 @@ impl<Kind> Error<Kind> {
130170 self . meta . location
131171 }
132172
173+ /// Records the byte offset in the input stream where this error was detected.
174+ ///
175+ /// Composes with [`Error::in_field`]: both may be set independently and
176+ /// are rendered together in `Display` output when present.
177+ ///
178+ /// Primarily intended for decode and encode errors on wire-format input,
179+ /// where pointing at the offending byte materially aids triage and is
180+ /// strictly more informative than the source code location alone.
181+ ///
182+ /// Available only when the `alloc` feature is enabled.
183+ #[ cfg( feature = "alloc" ) ]
184+ #[ cold]
185+ #[ must_use]
186+ pub fn at_offset ( mut self , offset : usize ) -> Self {
187+ let field = self . meta . position . and_then ( |p| p. field ) ;
188+ self . meta . position = Some ( ErrorPosition {
189+ offset : Some ( offset) ,
190+ field,
191+ } ) ;
192+ self
193+ }
194+
195+ /// Records the symbolic field or sub-PDU name being processed when this
196+ /// error occurred.
197+ ///
198+ /// Composes with [`Error::at_offset`]: both may be set independently and
199+ /// are rendered together in `Display` output when present.
200+ ///
201+ /// Dotted notation may be used for nested PDU navigation,
202+ /// e.g. `"ServerSecurityData.serverRandom"`.
203+ ///
204+ /// Available only when the `alloc` feature is enabled.
205+ #[ cfg( feature = "alloc" ) ]
206+ #[ cold]
207+ #[ must_use]
208+ pub fn in_field ( mut self , field : & ' static str ) -> Self {
209+ let offset = self . meta . position . and_then ( |p| p. offset ) ;
210+ self . meta . position = Some ( ErrorPosition {
211+ offset,
212+ field : Some ( field) ,
213+ } ) ;
214+ self
215+ }
216+
217+ /// Returns the byte offset and/or field-context position at which this
218+ /// error was constructed, if either was recorded via [`Error::at_offset`]
219+ /// or [`Error::in_field`].
220+ ///
221+ /// Available only when the `alloc` feature is enabled.
222+ #[ cfg( feature = "alloc" ) ]
223+ pub fn position ( & self ) -> Option < & ErrorPosition > {
224+ self . meta . position . as_ref ( )
225+ }
226+
133227 pub fn set_context ( & mut self , context : & ' static str ) {
134228 #[ cfg( feature = "alloc" ) ]
135229 {
@@ -160,7 +254,26 @@ where
160254 self . meta. location. file( ) ,
161255 self . meta. location. line( ) ,
162256 self . kind
163- )
257+ ) ?;
258+ match self . meta . position {
259+ Some ( ErrorPosition {
260+ offset : Some ( o) ,
261+ field : Some ( name) ,
262+ } ) => write ! ( f, " (at offset {o} in {name:?})" ) ,
263+ Some ( ErrorPosition {
264+ offset : Some ( o) ,
265+ field : None ,
266+ } ) => write ! ( f, " (at offset {o})" ) ,
267+ Some ( ErrorPosition {
268+ offset : None ,
269+ field : Some ( name) ,
270+ } ) => write ! ( f, " (in {name:?})" ) ,
271+ Some ( ErrorPosition {
272+ offset : None ,
273+ field : None ,
274+ } )
275+ | None => Ok ( ( ) ) ,
276+ }
164277 }
165278 #[ cfg( not( feature = "alloc" ) ) ]
166279 {
0 commit comments