@@ -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.
@@ -56,9 +90,14 @@ impl<Kind: fmt::Debug> fmt::Debug for Error<Kind> {
5690 fn fmt ( & self , f : & mut fmt:: Formatter < ' _ > ) -> fmt:: Result {
5791 let mut dbg = f. debug_struct ( "Error" ) ;
5892 #[ cfg( feature = "alloc" ) ]
59- dbg. field ( "context" , & self . meta . context )
60- . field ( "kind" , & self . kind )
61- . field ( "source" , & self . meta . source ) ;
93+ {
94+ dbg. field ( "context" , & self . meta . context )
95+ . field ( "kind" , & self . kind )
96+ . field ( "source" , & self . meta . source ) ;
97+ if let Some ( position) = & self . meta . position {
98+ dbg. field ( "position" , position) ;
99+ }
100+ }
62101 #[ cfg( not( feature = "alloc" ) ) ]
63102 dbg. field ( "context" , & self . context ) . field ( "kind" , & self . kind ) ;
64103 dbg. finish ( )
@@ -77,6 +116,7 @@ impl<Kind> Error<Kind> {
77116 context,
78117 location : core:: panic:: Location :: caller ( ) ,
79118 source : None ,
119+ position : None ,
80120 } ) ,
81121 #[ cfg( not( feature = "alloc" ) ) ]
82122 context,
@@ -141,6 +181,60 @@ impl<Kind> Error<Kind> {
141181 }
142182 }
143183
184+ /// Records the byte offset in the input stream where this error was detected.
185+ ///
186+ /// Composes with [`Error::in_field`]: both may be set independently and
187+ /// are rendered together in `Display` output when present.
188+ ///
189+ /// Primarily intended for decode and encode errors on wire-format input,
190+ /// where pointing at the offending byte materially aids triage and is
191+ /// strictly more informative than the source code location alone.
192+ ///
193+ /// Available only when the `alloc` feature is enabled.
194+ #[ cfg( feature = "alloc" ) ]
195+ #[ cold]
196+ #[ must_use]
197+ pub fn at_offset ( mut self , offset : usize ) -> Self {
198+ let field = self . meta . position . and_then ( |p| p. field ) ;
199+ self . meta . position = Some ( ErrorPosition {
200+ offset : Some ( offset) ,
201+ field,
202+ } ) ;
203+ self
204+ }
205+
206+ /// Records the symbolic field or sub-PDU name being processed when this
207+ /// error occurred.
208+ ///
209+ /// Composes with [`Error::at_offset`]: both may be set independently and
210+ /// are rendered together in `Display` output when present.
211+ ///
212+ /// Dotted notation may be used for nested PDU navigation,
213+ /// e.g. `"ServerSecurityData.serverRandom"`.
214+ ///
215+ /// Available only when the `alloc` feature is enabled.
216+ #[ cfg( feature = "alloc" ) ]
217+ #[ cold]
218+ #[ must_use]
219+ pub fn in_field ( mut self , field : & ' static str ) -> Self {
220+ let offset = self . meta . position . and_then ( |p| p. offset ) ;
221+ self . meta . position = Some ( ErrorPosition {
222+ offset,
223+ field : Some ( field) ,
224+ } ) ;
225+ self
226+ }
227+
228+ /// Returns the byte offset and/or field-context position at which this
229+ /// error was constructed, if either was recorded via [`Error::at_offset`]
230+ /// or [`Error::in_field`].
231+ ///
232+ /// Available only when the `alloc` feature is enabled.
233+ #[ cfg( feature = "alloc" ) ]
234+ pub fn position ( & self ) -> Option < & ErrorPosition > {
235+ self . meta . position . as_ref ( )
236+ }
237+
144238 pub fn set_context ( & mut self , context : & ' static str ) {
145239 #[ cfg( feature = "alloc" ) ]
146240 {
@@ -171,7 +265,26 @@ where
171265 self . meta. location. file( ) ,
172266 self . meta. location. line( ) ,
173267 self . kind
174- )
268+ ) ?;
269+ match self . meta . position {
270+ Some ( ErrorPosition {
271+ offset : Some ( o) ,
272+ field : Some ( name) ,
273+ } ) => write ! ( f, " (at offset {o} in {name:?})" ) ,
274+ Some ( ErrorPosition {
275+ offset : Some ( o) ,
276+ field : None ,
277+ } ) => write ! ( f, " (at offset {o})" ) ,
278+ Some ( ErrorPosition {
279+ offset : None ,
280+ field : Some ( name) ,
281+ } ) => write ! ( f, " (in {name:?})" ) ,
282+ Some ( ErrorPosition {
283+ offset : None ,
284+ field : None ,
285+ } )
286+ | None => Ok ( ( ) ) ,
287+ }
175288 }
176289 #[ cfg( not( feature = "alloc" ) ) ]
177290 {
0 commit comments