Skip to content

Commit 8e79274

Browse files
committed
compiler: forbid fragment probabilities of 0
We can cause panics in the compiler comparing OrdF64s which are NaN, which we can produce by parsing ors where every child has weight 0. Simply make it a parse error to have any zero-probability branches. In theory somebody might want an or where some fragments have positive probability and others are 0, indicating "maximally pessimize these". But it has never occurred to me to do this, and it's easy to simulate this by using 100s and 1s, say.
1 parent 4cf935d commit 8e79274

File tree

3 files changed

+32
-5
lines changed

3 files changed

+32
-5
lines changed

src/expression/error.rs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -197,6 +197,13 @@ pub enum ParseNumError {
197197
StdParse(num::ParseIntError),
198198
/// Number had a leading zero, + or -.
199199
InvalidLeadingDigit(char),
200+
/// Number was 0 in a context where zero is not allowed.
201+
///
202+
/// Be aware that locktimes have their own error types and do not use this variant.
203+
IllegalZero {
204+
/// A description of the location where 0 was not allowed.
205+
context: &'static str,
206+
},
200207
}
201208

202209
impl fmt::Display for ParseNumError {
@@ -206,6 +213,9 @@ impl fmt::Display for ParseNumError {
206213
ParseNumError::InvalidLeadingDigit(ch) => {
207214
write!(f, "numbers must start with 1-9, not {}", ch)
208215
}
216+
ParseNumError::IllegalZero { context } => {
217+
write!(f, "{} may not be 0", context)
218+
}
209219
}
210220
}
211221
}
@@ -216,6 +226,7 @@ impl std::error::Error for ParseNumError {
216226
match self {
217227
ParseNumError::StdParse(ref e) => Some(e),
218228
ParseNumError::InvalidLeadingDigit(..) => None,
229+
ParseNumError::IllegalZero { .. } => None,
219230
}
220231
}
221232
}

src/expression/mod.rs

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -678,11 +678,10 @@ impl<'a> Tree<'a> {
678678
}
679679
}
680680

681-
/// Parse a string as a u32, for timelocks or thresholds
682-
pub fn parse_num(s: &str) -> Result<u32, ParseNumError> {
681+
/// Parse a string as a u32, forbidding zero.
682+
pub fn parse_num_nonzero(s: &str, context: &'static str) -> Result<u32, ParseNumError> {
683683
if s == "0" {
684-
// Special-case 0 since it is the only number which may start with a leading zero.
685-
return Ok(0);
684+
return Err(ParseNumError::IllegalZero { context });
686685
}
687686
if let Some(ch) = s.chars().next() {
688687
if !('1'..='9').contains(&ch) {
@@ -692,6 +691,15 @@ pub fn parse_num(s: &str) -> Result<u32, ParseNumError> {
692691
u32::from_str(s).map_err(ParseNumError::StdParse)
693692
}
694693

694+
/// Parse a string as a u32, for timelocks or thresholds
695+
pub fn parse_num(s: &str) -> Result<u32, ParseNumError> {
696+
if s == "0" {
697+
// Special-case 0 since it is the only number which may start with a leading zero.
698+
return Ok(0);
699+
}
700+
parse_num_nonzero(s, "")
701+
}
702+
695703
#[cfg(test)]
696704
mod tests {
697705
use super::*;
@@ -772,6 +780,7 @@ mod tests {
772780
#[test]
773781
fn test_parse_num() {
774782
assert!(parse_num("0").is_ok());
783+
assert!(parse_num_nonzero("0", "").is_err());
775784
assert!(parse_num("00").is_err());
776785
assert!(parse_num("0000").is_err());
777786
assert!(parse_num("06").is_err());

src/policy/concrete.rs

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -883,7 +883,7 @@ impl<Pk: FromStrKey> expression::FromTree for Policy<Pk> {
883883

884884
let frag_prob = match frag_prob {
885885
None => 1,
886-
Some(s) => expression::parse_num(s)
886+
Some(s) => expression::parse_num_nonzero(s, "fragment probability")
887887
.map_err(From::from)
888888
.map_err(Error::Parse)? as usize,
889889
};
@@ -1231,4 +1231,11 @@ mod tests {
12311231
// This implicitly tests the check_timelocks API (has height and time locks).
12321232
let _ = Policy::<String>::from_str("and(after(10),after(500000000))").unwrap();
12331233
}
1234+
1235+
#[test]
1236+
fn parse_zero_disjunction() {
1237+
"or(0@pk(09),0@TRIVIAL)"
1238+
.parse::<Policy<String>>()
1239+
.unwrap_err();
1240+
}
12341241
}

0 commit comments

Comments
 (0)