Skip to content

Commit e79a41a

Browse files
authored
Merge pull request #620 from epage/negative
fix: Dont crash on bad negative bounds
2 parents eec8d6d + da18a36 commit e79a41a

File tree

4 files changed

+104
-28
lines changed

4 files changed

+104
-28
lines changed

src/error.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -174,7 +174,7 @@ impl ConfigError {
174174
}
175175

176176
/// Alias for a `Result` with the error type set to `ConfigError`.
177-
pub(crate) type Result<T> = result::Result<T, ConfigError>;
177+
pub(crate) type Result<T, E = ConfigError> = result::Result<T, E>;
178178

179179
// Forward Debug to Display for readable panic! messages
180180
impl fmt::Debug for ConfigError {

src/path/mod.rs

Lines changed: 24 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -40,11 +40,14 @@ impl std::fmt::Display for ParseError {
4040

4141
impl std::error::Error for ParseError {}
4242

43-
fn sindex_to_uindex(index: isize, len: usize) -> usize {
43+
/// Convert a relative index into an absolute index
44+
fn abs_index(index: isize, len: usize) -> Result<usize, usize> {
4445
if index >= 0 {
45-
index as usize
46+
Ok(index as usize)
47+
} else if let Some(index) = len.checked_sub(index.unsigned_abs()) {
48+
Ok(index)
4649
} else {
47-
len - index.unsigned_abs()
50+
Err((len as isize + index).unsigned_abs())
4851
}
4952
}
5053

@@ -80,13 +83,8 @@ impl Expression {
8083
Self::Subscript(expr, index) => match expr.get(root) {
8184
Some(value) => match value.kind {
8285
ValueKind::Array(ref array) => {
83-
let index = sindex_to_uindex(index, array.len());
84-
85-
if index >= array.len() {
86-
None
87-
} else {
88-
Some(&array[index])
89-
}
86+
let index = abs_index(index, array.len()).ok()?;
87+
array.get(index)
9088
}
9189

9290
_ => None,
@@ -141,7 +139,7 @@ impl Expression {
141139

142140
match value.kind {
143141
ValueKind::Array(ref mut array) => {
144-
let index = sindex_to_uindex(index, array.len());
142+
let index = abs_index(index, array.len()).ok()?;
145143

146144
if index >= array.len() {
147145
array.resize(index + 1, Value::new(None, ValueKind::Nil));
@@ -216,10 +214,21 @@ impl Expression {
216214
}
217215

218216
if let ValueKind::Array(ref mut array) = parent.kind {
219-
let uindex = sindex_to_uindex(index, array.len());
220-
if uindex >= array.len() {
221-
array.resize(uindex + 1, Value::new(None, ValueKind::Nil));
222-
}
217+
let uindex = match abs_index(index, array.len()) {
218+
Ok(uindex) => {
219+
if uindex >= array.len() {
220+
array.resize(uindex + 1, Value::new(None, ValueKind::Nil));
221+
}
222+
uindex
223+
}
224+
Err(insertion) => {
225+
array.splice(
226+
0..0,
227+
(0..insertion).map(|_| Value::new(None, ValueKind::Nil)),
228+
);
229+
0
230+
}
231+
};
223232

224233
array[uindex] = value;
225234
}

tests/testsuite/errors.rs

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,52 @@ use snapbox::{assert_data_eq, str};
33

44
use config::{Config, ConfigError, File, FileFormat, Map, Value};
55

6+
#[test]
7+
#[cfg(feature = "json")]
8+
fn test_error_path_index_bounds() {
9+
let c = Config::builder()
10+
.add_source(File::from_str(
11+
r#"
12+
{
13+
"arr": [1]
14+
}
15+
"#,
16+
FileFormat::Json,
17+
))
18+
.build()
19+
.unwrap();
20+
21+
let res = c.get::<usize>("arr[2]");
22+
assert!(res.is_err());
23+
assert_data_eq!(
24+
res.unwrap_err().to_string(),
25+
str![[r#"configuration property "arr[2]" not found"#]]
26+
);
27+
}
28+
29+
#[test]
30+
#[cfg(feature = "json")]
31+
fn test_error_path_index_negative_bounds() {
32+
let c = Config::builder()
33+
.add_source(File::from_str(
34+
r#"
35+
{
36+
"arr": []
37+
}
38+
"#,
39+
FileFormat::Json,
40+
))
41+
.build()
42+
.unwrap();
43+
44+
let res = c.get::<usize>("arr[-1]");
45+
assert!(res.is_err());
46+
assert_data_eq!(
47+
res.unwrap_err().to_string(),
48+
str![[r#"configuration property "arr[-1]" not found"#]]
49+
);
50+
}
51+
652
#[test]
753
#[cfg(feature = "json")]
854
fn test_error_parse() {

tests/testsuite/set.rs

Lines changed: 33 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -65,42 +65,63 @@ fn test_set_scalar_path() {
6565
#[cfg(feature = "json")]
6666
fn test_set_arr_path() {
6767
let config = Config::builder()
68-
.set_override("items[0].name", "Ivan")
68+
.set_override("present[0].name", "Ivan")
6969
.unwrap()
70-
.set_override("data[0].things[1].name", "foo")
70+
.set_override("absent[0].things[1].name", "foo")
7171
.unwrap()
72-
.set_override("data[0].things[1].value", 42)
72+
.set_override("absent[0].things[1].value", 42)
7373
.unwrap()
74-
.set_override("data[1]", 0)
74+
.set_override("absent[1]", 0)
7575
.unwrap()
76-
.set_override("items[2]", "George")
76+
.set_override("present[2]", "George")
77+
.unwrap()
78+
.set_override("reverse[-1]", "Bob")
79+
.unwrap()
80+
.set_override("reverse[-2]", "Alice")
81+
.unwrap()
82+
.set_override("empty[-1]", "Bob")
83+
.unwrap()
84+
.set_override("empty[-2]", "Alice")
7785
.unwrap()
7886
.add_source(File::from_str(
7987
r#"
8088
{
81-
"items": [
89+
"present": [
8290
{
8391
"name": "1"
8492
},
8593
{
8694
"name": "2"
8795
}
88-
]
96+
],
97+
"reverse": [
98+
{
99+
"name": "l1"
100+
},
101+
{
102+
"name": "l2"
103+
}
104+
],
105+
"empty": []
89106
}
90107
"#,
91108
FileFormat::Json,
92109
))
93110
.build()
94111
.unwrap();
95112

96-
assert_eq!(config.get("items[0].name").ok(), Some("Ivan".to_owned()));
113+
assert_eq!(config.get("present[0].name").ok(), Some("Ivan".to_owned()));
97114
assert_eq!(
98-
config.get("data[0].things[1].name").ok(),
115+
config.get("absent[0].things[1].name").ok(),
99116
Some("foo".to_owned())
100117
);
101-
assert_eq!(config.get("data[0].things[1].value").ok(), Some(42));
102-
assert_eq!(config.get("data[1]").ok(), Some(0));
103-
assert_eq!(config.get("items[2]").ok(), Some("George".to_owned()));
118+
assert_eq!(config.get("absent[0].things[1].value").ok(), Some(42));
119+
assert_eq!(config.get("absent[1]").ok(), Some(0));
120+
assert_eq!(config.get("present[2]").ok(), Some("George".to_owned()));
121+
assert_eq!(config.get("reverse[1]").ok(), Some("Bob".to_owned()));
122+
assert_eq!(config.get("reverse[0]").ok(), Some("Alice".to_owned()));
123+
assert_eq!(config.get("empty[1]").ok(), Some("Bob".to_owned()));
124+
assert_eq!(config.get("empty[0]").ok(), Some("Alice".to_owned()));
104125
}
105126

106127
#[test]

0 commit comments

Comments
 (0)