Skip to content

Commit ed3fb45

Browse files
committed
Merge rust-bitcoin/rust-bitcoin#1129: Parse int error
071a1c0 Implement string parsing for `Sequence` (Martin Habovstiak) c39bc39 Extend `ParseIntError` to carry more context (Martin Habovstiak) Pull request description: When debugging parsing errors it's very useful to know some context: what the input was and what integer type was parsed. `ParseIntError` from `core` doesn't contain this information. In this commit a custom `ParseIntError` type is crated that carries the one from `core` as well as additional information. Its `Display` implementation displays this additional information as a well-formed English sentence to aid users with understanding the problem. A helper function parses any integer type from common string types returning the new `ParseIntError` type on error. To clean up the error code a bit some new macros are added and used. New modules are added to organize the types, functions and macros. Closes #1113 Depends on #994 ACKs for top commit: apoelstra: ACK 071a1c0 tcharding: ACK 071a1c0 Tree-SHA512: 31cb84b9e4d5fe3bdeb1cd48b85da2cbe9b9d17d93d029c2f95e0eed5b8842d7a553afafcf8b4a87c075aa53cf0274776e893bed6dca37e7dbc2e1ee1d602b8e
2 parents a550fa8 + 071a1c0 commit ed3fb45

File tree

7 files changed

+181
-62
lines changed

7 files changed

+181
-62
lines changed

src/blockdata/locktime.rs

Lines changed: 22 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -21,12 +21,14 @@ use core::{mem, fmt};
2121
use core::cmp::{PartialOrd, Ordering};
2222
use core::convert::TryFrom;
2323
use core::str::FromStr;
24-
use core::num::ParseIntError;
24+
use crate::error::ParseIntError;
25+
use crate::parse;
2526

2627
use crate::consensus::encode::{self, Decodable, Encodable};
2728
use crate::io::{self, Read, Write};
2829
use crate::prelude::*;
2930
use crate::internal_macros::write_err;
31+
use crate::impl_parse_str_through_int;
3032

3133
/// The Threshold for deciding whether a lock time value is a height or a time (see [Bitcoin Core]).
3234
///
@@ -134,29 +136,7 @@ impl From<PackedLockTime> for u32 {
134136
}
135137
}
136138

137-
impl FromStr for PackedLockTime {
138-
type Err = ParseIntError;
139-
140-
fn from_str(s: &str) -> Result<Self, Self::Err> {
141-
s.parse().map(PackedLockTime)
142-
}
143-
}
144-
145-
impl TryFrom<&str> for PackedLockTime {
146-
type Error = ParseIntError;
147-
148-
fn try_from(s: &str) -> Result<Self, Self::Error> {
149-
PackedLockTime::from_str(s)
150-
}
151-
}
152-
153-
impl TryFrom<String> for PackedLockTime {
154-
type Error = ParseIntError;
155-
156-
fn try_from(s: String) -> Result<Self, Self::Error> {
157-
PackedLockTime::from_str(&s)
158-
}
159-
}
139+
impl_parse_str_through_int!(PackedLockTime);
160140

161141
impl fmt::LowerHex for PackedLockTime {
162142
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
@@ -366,29 +346,7 @@ impl LockTime {
366346
}
367347
}
368348

369-
impl FromStr for LockTime {
370-
type Err = ParseIntError;
371-
372-
fn from_str(s: &str) -> Result<Self, Self::Err> {
373-
s.parse().map(LockTime::from_consensus)
374-
}
375-
}
376-
377-
impl TryFrom<&str> for LockTime {
378-
type Error = ParseIntError;
379-
380-
fn try_from(s: &str) -> Result<Self, Self::Error> {
381-
LockTime::from_str(s)
382-
}
383-
}
384-
385-
impl TryFrom<String> for LockTime {
386-
type Error = ParseIntError;
387-
388-
fn try_from(s: String) -> Result<Self, Self::Error> {
389-
LockTime::from_str(&s)
390-
}
391-
}
349+
impl_parse_str_through_int!(LockTime, from_consensus);
392350

393351
impl From<Height> for LockTime {
394352
fn from(h: Height) -> Self {
@@ -503,7 +461,7 @@ impl FromStr for Height {
503461
type Err = Error;
504462

505463
fn from_str(s: &str) -> Result<Self, Self::Err> {
506-
let n = s.parse::<u32>().map_err(|e| Error::Parse(e, s.to_owned()))?;
464+
let n = parse::int(s)?;
507465
Height::from_consensus(n)
508466
}
509467
}
@@ -512,15 +470,16 @@ impl TryFrom<&str> for Height {
512470
type Error = Error;
513471

514472
fn try_from(s: &str) -> Result<Self, Self::Error> {
515-
Height::from_str(s)
473+
let n = parse::int(s)?;
474+
Height::from_consensus(n)
516475
}
517476
}
518477

519478
impl TryFrom<String> for Height {
520479
type Error = Error;
521480

522481
fn try_from(s: String) -> Result<Self, Self::Error> {
523-
let n = s.parse::<u32>().map_err(|e| Error::Parse(e, s))?;
482+
let n = parse::int(s)?;
524483
Height::from_consensus(n)
525484
}
526485
}
@@ -585,7 +544,7 @@ impl FromStr for Time {
585544
type Err = Error;
586545

587546
fn from_str(s: &str) -> Result<Self, Self::Err> {
588-
let n = s.parse::<u32>().map_err(|e| Error::Parse(e, s.to_owned()))?;
547+
let n = parse::int(s)?;
589548
Time::from_consensus(n)
590549
}
591550
}
@@ -594,15 +553,16 @@ impl TryFrom<&str> for Time {
594553
type Error = Error;
595554

596555
fn try_from(s: &str) -> Result<Self, Self::Error> {
597-
Time::from_str(s)
556+
let n = parse::int(s)?;
557+
Time::from_consensus(n)
598558
}
599559
}
600560

601561
impl TryFrom<String> for Time {
602562
type Error = Error;
603563

604564
fn try_from(s: String) -> Result<Self, Self::Error> {
605-
let n = s.parse::<u32>().map_err(|e| Error::Parse(e, s))?;
565+
let n = parse::int(s)?;
606566
Time::from_consensus(n)
607567
}
608568
}
@@ -626,7 +586,7 @@ pub enum Error {
626586
/// An error occurred while operating on lock times.
627587
Operation(OperationError),
628588
/// An error occurred while parsing a string into an `u32`.
629-
Parse(ParseIntError, String),
589+
Parse(ParseIntError),
630590
}
631591

632592
impl fmt::Display for Error {
@@ -636,7 +596,7 @@ impl fmt::Display for Error {
636596
match *self {
637597
Conversion(ref e) => write_err!(f, "error converting lock time value"; e),
638598
Operation(ref e) => write_err!(f, "error during lock time operation"; e),
639-
Parse(ref e, ref s) => write_err!(f, "error parsing string: {}", s; e),
599+
Parse(ref e) => write_err!(f, "failed to parse lock time from string"; e),
640600
}
641601
}
642602
}
@@ -650,7 +610,7 @@ impl std::error::Error for Error {
650610
match *self {
651611
Conversion(ref e) => Some(e),
652612
Operation(ref e) => Some(e),
653-
Parse(ref e, _) => Some(e),
613+
Parse(ref e) => Some(e),
654614
}
655615
}
656616
}
@@ -667,6 +627,12 @@ impl From<OperationError> for Error {
667627
}
668628
}
669629

630+
impl From<ParseIntError> for Error {
631+
fn from(e: ParseIntError) -> Self {
632+
Error::Parse(e)
633+
}
634+
}
635+
670636
/// An error that occurs when converting a `u32` to a lock time variant.
671637
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
672638
pub struct ConversionError {

src/blockdata/transaction.rs

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ use crate::hash_types::{Sighash, Txid, Wtxid};
3232
use crate::VarInt;
3333
use crate::util::sighash::UINT256_ONE;
3434
use crate::internal_macros::{impl_consensus_encoding, serde_string_impl, serde_struct_human_string_impl, write_err};
35+
use crate::impl_parse_str_through_int;
3536

3637
#[cfg(doc)]
3738
use crate::util::sighash::SchnorrSighashType;
@@ -107,7 +108,7 @@ pub enum ParseOutPointError {
107108
/// Error in TXID part.
108109
Txid(hashes::hex::Error),
109110
/// Error in vout part.
110-
Vout(::core::num::ParseIntError),
111+
Vout(crate::error::ParseIntError),
111112
/// Error in general format.
112113
Format,
113114
/// Size exceeds max.
@@ -151,7 +152,7 @@ fn parse_vout(s: &str) -> Result<u32, ParseOutPointError> {
151152
return Err(ParseOutPointError::VoutNotCanonical);
152153
}
153154
}
154-
s.parse().map_err(ParseOutPointError::Vout)
155+
crate::parse::int(s).map_err(ParseOutPointError::Vout)
155156
}
156157

157158
impl core::str::FromStr for OutPoint {
@@ -422,6 +423,8 @@ impl fmt::Display for RelativeLockTimeError {
422423
}
423424
}
424425

426+
impl_parse_str_through_int!(Sequence);
427+
425428
#[cfg(feature = "std")]
426429
#[cfg_attr(docsrs, doc(cfg(feature = "std")))]
427430
impl std::error::Error for RelativeLockTimeError {
@@ -1308,7 +1311,7 @@ mod tests {
13081311
assert_eq!(OutPoint::from_str("5df6e0e2761359d30a8275058e299fcc0381534545f55cf43e41983f5d4c945X:1"),
13091312
Err(ParseOutPointError::Txid(Txid::from_hex("5df6e0e2761359d30a8275058e299fcc0381534545f55cf43e41983f5d4c945X").unwrap_err())));
13101313
assert_eq!(OutPoint::from_str("5df6e0e2761359d30a8275058e299fcc0381534545f55cf43e41983f5d4c9456:lol"),
1311-
Err(ParseOutPointError::Vout(u32::from_str("lol").unwrap_err())));
1314+
Err(ParseOutPointError::Vout(crate::parse::int::<u32, _>("lol").unwrap_err())));
13121315

13131316
assert_eq!(OutPoint::from_str("5df6e0e2761359d30a8275058e299fcc0381534545f55cf43e41983f5d4c9456:42"),
13141317
Ok(OutPoint{

src/error.rs

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
//! Contains error types and other error handling tools.
2+
3+
pub use crate::parse::ParseIntError;
4+
5+
/// Impls std::error::Error for the specified type with appropriate attributes, possibly returning
6+
/// source.
7+
#[macro_export]
8+
macro_rules! impl_std_error {
9+
// No source available
10+
($type:ty) => {
11+
#[cfg(feature = "std")]
12+
#[cfg_attr(docsrs, doc(cfg(feature = "std")))]
13+
impl std::error::Error for $type {}
14+
};
15+
// Struct with $field as source
16+
($type:ty, $field:ident) => {
17+
#[cfg(feature = "std")]
18+
#[cfg_attr(docsrs, doc(cfg(feature = "std")))]
19+
impl std::error::Error for $type {
20+
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
21+
Some(&self.$field)
22+
}
23+
}
24+
};
25+
}

src/internal_macros.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -581,7 +581,7 @@ pub(crate) use user_enum;
581581
/// because `e.source()` is only available in std builds, without this macro the error source is
582582
/// lost for no-std builds.
583583
macro_rules! write_err {
584-
($writer:expr, $string:literal $(, $args:expr),*; $source:expr) => {
584+
($writer:expr, $string:literal $(, $args:expr)*; $source:expr) => {
585585
{
586586
#[cfg(feature = "std")]
587587
{

src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,11 +78,13 @@ mod test_macros;
7878
mod internal_macros;
7979
#[cfg(feature = "serde")]
8080
mod serde_utils;
81+
mod parse;
8182

8283
#[macro_use]
8384
pub mod network;
8485
pub mod blockdata;
8586
pub mod consensus;
87+
pub mod error;
8688
pub mod hash_types;
8789
pub mod policy;
8890
pub mod util;

src/parse.rs

Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
use crate::internal_macros::write_err;
2+
use crate::impl_std_error;
3+
use core::fmt;
4+
use core::str::FromStr;
5+
use core::convert::TryFrom;
6+
use crate::prelude::*;
7+
8+
/// Error with rich context returned when a string can't be parsed as an integer.
9+
///
10+
/// This is an extension of [`core::num::ParseIntError`], which carries the input that failed to
11+
/// parse as well as type information. As a result it provides very informative error messages that
12+
/// make it easier to understand the problem and correct mistakes.
13+
///
14+
/// Note that this is larger than the type from `core` so if it's passed through a deep call stack
15+
/// in a performance-critical application you may want to box it or throw away the context by
16+
/// converting to `core` type.
17+
#[derive(Debug, Clone, Eq, PartialEq)]
18+
pub struct ParseIntError {
19+
input: String,
20+
// for displaying - see Display impl with nice error message below
21+
bits: u8,
22+
// We could represent this as a single bit but it wouldn't actually derease the cost of moving
23+
// the struct because String contains pointers so there will be padding of bits at least
24+
// pointer_size - 1 bytes: min 1B in practice.
25+
is_signed: bool,
26+
source: core::num::ParseIntError,
27+
}
28+
29+
impl ParseIntError {
30+
/// Returns the input that was attempted to be parsed.
31+
pub fn input(&self) -> &str {
32+
&self.input
33+
}
34+
}
35+
36+
impl From<ParseIntError> for core::num::ParseIntError {
37+
fn from(value: ParseIntError) -> Self {
38+
value.source
39+
}
40+
}
41+
42+
impl AsRef<core::num::ParseIntError> for ParseIntError {
43+
fn as_ref(&self) -> &core::num::ParseIntError {
44+
&self.source
45+
}
46+
}
47+
48+
impl fmt::Display for ParseIntError {
49+
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
50+
let signed = if self.is_signed { "signed" } else { "unsigned" };
51+
let n = if self.bits == 8 { "n" } else { "" };
52+
write_err!(f, "failed to parse '{}' as a{} {}-bit {} integer", self.input, n, self.bits, signed; self.source)
53+
}
54+
}
55+
56+
/// Not strictly neccessary but serves as a lint - avoids weird behavior if someone accidentally
57+
/// passes non-integer to the `parse()` function.
58+
pub(crate) trait Integer: FromStr<Err=core::num::ParseIntError> + TryFrom<i8> + Sized {}
59+
60+
macro_rules! impl_integer {
61+
($($type:ty),* $(,)?) => {
62+
$(
63+
impl Integer for $type {}
64+
)*
65+
}
66+
}
67+
68+
impl_integer!(u8, i8, u16, i16, u32, i32, u64, i64, u128, i128);
69+
70+
/// Parses the input string as an integer returning an error carrying rich context.
71+
///
72+
/// If the caller owns `String` or `Box<str>` which is not used later it's better to pass it as
73+
/// owned since it avoids allocation in error case.
74+
pub(crate) fn int<T: Integer, S: AsRef<str> + Into<String>>(s: S) -> Result<T, ParseIntError> {
75+
s.as_ref().parse().map_err(|error| {
76+
ParseIntError {
77+
input: s.into(),
78+
bits: u8::try_from(core::mem::size_of::<T>() * 8).expect("max is 128 bits for u128"),
79+
// We detect if the type is signed by checking if -1 can be represented by it
80+
// this way we don't have to implement special traits and optimizer will get rid of the
81+
// computation.
82+
is_signed: T::try_from(-1i8).is_ok(),
83+
source: error,
84+
}
85+
})
86+
}
87+
88+
impl_std_error!(ParseIntError, source);
89+
90+
/// Implements `TryFrom<$from> for $to` using `parse::int`, mapping the output using `fn`
91+
#[macro_export]
92+
macro_rules! impl_tryfrom_str_through_int_single {
93+
($($from:ty, $to:ident $(, $fn:ident)?);*) => {
94+
$(
95+
impl core::convert::TryFrom<$from> for $to {
96+
type Error = $crate::error::ParseIntError;
97+
98+
fn try_from(s: $from) -> Result<Self, Self::Error> {
99+
$crate::parse::int(s).map($to $(:: $fn)?)
100+
}
101+
}
102+
)*
103+
}
104+
}
105+
106+
/// Implements `FromStr` and `TryFrom<{&str, String, Box<str>}> for $to` using `parse::int`, mapping the output using `fn`
107+
///
108+
/// The `Error` type is `ParseIntError`
109+
#[macro_export]
110+
macro_rules! impl_parse_str_through_int {
111+
($to:ident $(, $fn:ident)?) => {
112+
$crate::impl_tryfrom_str_through_int_single!(&str, $to $(, $fn)?; String, $to $(, $fn)?; Box<str>, $to $(, $fn)?);
113+
114+
impl core::str::FromStr for $to {
115+
type Err = $crate::error::ParseIntError;
116+
117+
fn from_str(s: &str) -> Result<Self, Self::Err> {
118+
$crate::parse::int(s).map($to $(:: $fn)?)
119+
}
120+
}
121+
122+
}
123+
}

0 commit comments

Comments
 (0)