Skip to content

Commit 6423548

Browse files
committed
Fix a bunch of failure cases in terminfo
Replace all potentially-failing operations with Err returns and add tests. Remove the Char parameter type; characters are represented as Numbers. Fix integer constants to work properly when there are multiple constants in the same capability string. Tweak loop to use iterators instead of indexing into cap.
1 parent e990239 commit 6423548

File tree

1 file changed

+161
-84
lines changed

1 file changed

+161
-84
lines changed

src/libextra/terminfo/parm.rs

Lines changed: 161 additions & 84 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,6 @@ enum States {
3131
/// Types of parameters a capability can use
3232
pub enum Param {
3333
String(~str),
34-
Char(char),
3534
Number(int)
3635
}
3736

@@ -64,13 +63,10 @@ impl Variables {
6463
pub fn expand(cap: &[u8], params: &[Param], vars: &mut Variables)
6564
-> Result<~[u8], ~str> {
6665
let mut state = Nothing;
67-
let mut i = 0;
6866

6967
// expanded cap will only rarely be larger than the cap itself
7068
let mut output = vec::with_capacity(cap.len());
7169

72-
let mut cur;
73-
7470
let mut stack: ~[Param] = ~[];
7571

7672
let mut intstate = ~[];
@@ -81,92 +77,123 @@ pub fn expand(cap: &[u8], params: &[Param], vars: &mut Variables)
8177
*dst = src;
8278
}
8379

84-
while i < cap.len() {
85-
cur = cap[i] as char;
80+
for cap.iter().transform(|&x| x).advance |c| {
81+
let cur = c as char;
8682
let mut old_state = state;
8783
match state {
8884
Nothing => {
8985
if cur == '%' {
9086
state = Percent;
9187
} else {
92-
output.push(cap[i]);
88+
output.push(c);
9389
}
9490
},
9591
Percent => {
9692
match cur {
97-
'%' => { output.push(cap[i]); state = Nothing },
98-
'c' => match stack.pop() {
99-
Char(c) => output.push(c as u8),
100-
_ => return Err(~"a non-char was used with %c")
101-
},
102-
's' => match stack.pop() {
103-
String(s) => output.push_all(s.as_bytes()),
104-
_ => return Err(~"a non-str was used with %s")
105-
},
106-
'd' => match stack.pop() {
107-
Number(x) => {
108-
let s = x.to_str();
109-
output.push_all(s.as_bytes())
93+
'%' => { output.push(c); state = Nothing },
94+
'c' => if stack.len() > 0 {
95+
match stack.pop() {
96+
// if c is 0, use 0200 (128) for ncurses compatibility
97+
Number(c) => output.push(if c == 0 { 128 } else { c } as u8),
98+
_ => return Err(~"a non-char was used with %c")
11099
}
111-
_ => return Err(~"a non-number was used with %d")
112-
},
100+
} else { return Err(~"stack is empty") },
101+
's' => if stack.len() > 0 {
102+
match stack.pop() {
103+
String(s) => output.push_all(s.as_bytes()),
104+
_ => return Err(~"a non-str was used with %s")
105+
}
106+
} else { return Err(~"stack is empty") },
107+
'd' => if stack.len() > 0 {
108+
match stack.pop() {
109+
Number(x) => {
110+
let s = x.to_str();
111+
output.push_all(s.as_bytes())
112+
}
113+
_ => return Err(~"a non-number was used with %d")
114+
}
115+
} else { return Err(~"stack is empty") },
113116
'p' => state = PushParam,
114117
'P' => state = SetVar,
115118
'g' => state = GetVar,
116119
'\'' => state = CharConstant,
117120
'{' => state = IntConstant,
118-
'l' => match stack.pop() {
119-
String(s) => stack.push(Number(s.len() as int)),
120-
_ => return Err(~"a non-str was used with %l")
121-
},
122-
'+' => match (stack.pop(), stack.pop()) {
123-
(Number(x), Number(y)) => stack.push(Number(x + y)),
124-
(_, _) => return Err(~"non-numbers on stack with +")
125-
},
126-
'-' => match (stack.pop(), stack.pop()) {
127-
(Number(x), Number(y)) => stack.push(Number(x - y)),
128-
(_, _) => return Err(~"non-numbers on stack with -")
129-
},
130-
'*' => match (stack.pop(), stack.pop()) {
131-
(Number(x), Number(y)) => stack.push(Number(x * y)),
132-
(_, _) => return Err(~"non-numbers on stack with *")
133-
},
134-
'/' => match (stack.pop(), stack.pop()) {
135-
(Number(x), Number(y)) => stack.push(Number(x / y)),
136-
(_, _) => return Err(~"non-numbers on stack with /")
137-
},
138-
'm' => match (stack.pop(), stack.pop()) {
139-
(Number(x), Number(y)) => stack.push(Number(x % y)),
140-
(_, _) => return Err(~"non-numbers on stack with %")
141-
},
142-
'&' => match (stack.pop(), stack.pop()) {
143-
(Number(x), Number(y)) => stack.push(Number(x & y)),
144-
(_, _) => return Err(~"non-numbers on stack with &")
145-
},
146-
'|' => match (stack.pop(), stack.pop()) {
147-
(Number(x), Number(y)) => stack.push(Number(x | y)),
148-
(_, _) => return Err(~"non-numbers on stack with |")
149-
},
150-
'A' => match (stack.pop(), stack.pop()) {
151-
(Number(0), Number(_)) => stack.push(Number(0)),
152-
(Number(_), Number(0)) => stack.push(Number(0)),
153-
(Number(_), Number(_)) => stack.push(Number(1)),
154-
_ => return Err(~"non-numbers on stack with logical and")
155-
},
156-
'O' => match (stack.pop(), stack.pop()) {
157-
(Number(0), Number(0)) => stack.push(Number(0)),
158-
(Number(_), Number(_)) => stack.push(Number(1)),
159-
_ => return Err(~"non-numbers on stack with logical or")
160-
},
161-
'!' => match stack.pop() {
162-
Number(0) => stack.push(Number(1)),
163-
Number(_) => stack.push(Number(0)),
164-
_ => return Err(~"non-number on stack with logical not")
165-
},
166-
'~' => match stack.pop() {
167-
Number(x) => stack.push(Number(!x)),
168-
_ => return Err(~"non-number on stack with %~")
169-
},
121+
'l' => if stack.len() > 0 {
122+
match stack.pop() {
123+
String(s) => stack.push(Number(s.len() as int)),
124+
_ => return Err(~"a non-str was used with %l")
125+
}
126+
} else { return Err(~"stack is empty") },
127+
'+' => if stack.len() > 1 {
128+
match (stack.pop(), stack.pop()) {
129+
(Number(x), Number(y)) => stack.push(Number(x + y)),
130+
(_, _) => return Err(~"non-numbers on stack with +")
131+
}
132+
} else { return Err(~"stack is empty") },
133+
'-' => if stack.len() > 1 {
134+
match (stack.pop(), stack.pop()) {
135+
(Number(x), Number(y)) => stack.push(Number(x - y)),
136+
(_, _) => return Err(~"non-numbers on stack with -")
137+
}
138+
} else { return Err(~"stack is empty") },
139+
'*' => if stack.len() > 1 {
140+
match (stack.pop(), stack.pop()) {
141+
(Number(x), Number(y)) => stack.push(Number(x * y)),
142+
(_, _) => return Err(~"non-numbers on stack with *")
143+
}
144+
} else { return Err(~"stack is empty") },
145+
'/' => if stack.len() > 1 {
146+
match (stack.pop(), stack.pop()) {
147+
(Number(x), Number(y)) => stack.push(Number(x / y)),
148+
(_, _) => return Err(~"non-numbers on stack with /")
149+
}
150+
} else { return Err(~"stack is empty") },
151+
'm' => if stack.len() > 1 {
152+
match (stack.pop(), stack.pop()) {
153+
(Number(x), Number(y)) => stack.push(Number(x % y)),
154+
(_, _) => return Err(~"non-numbers on stack with %")
155+
}
156+
} else { return Err(~"stack is empty") },
157+
'&' => if stack.len() > 1 {
158+
match (stack.pop(), stack.pop()) {
159+
(Number(x), Number(y)) => stack.push(Number(x & y)),
160+
(_, _) => return Err(~"non-numbers on stack with &")
161+
}
162+
} else { return Err(~"stack is empty") },
163+
'|' => if stack.len() > 1 {
164+
match (stack.pop(), stack.pop()) {
165+
(Number(x), Number(y)) => stack.push(Number(x | y)),
166+
(_, _) => return Err(~"non-numbers on stack with |")
167+
}
168+
} else { return Err(~"stack is empty") },
169+
'A' => if stack.len() > 1 {
170+
match (stack.pop(), stack.pop()) {
171+
(Number(0), Number(_)) => stack.push(Number(0)),
172+
(Number(_), Number(0)) => stack.push(Number(0)),
173+
(Number(_), Number(_)) => stack.push(Number(1)),
174+
_ => return Err(~"non-numbers on stack with logical and")
175+
}
176+
} else { return Err(~"stack is empty") },
177+
'O' => if stack.len() > 1 {
178+
match (stack.pop(), stack.pop()) {
179+
(Number(0), Number(0)) => stack.push(Number(0)),
180+
(Number(_), Number(_)) => stack.push(Number(1)),
181+
_ => return Err(~"non-numbers on stack with logical or")
182+
}
183+
} else { return Err(~"stack is empty") },
184+
'!' => if stack.len() > 0 {
185+
match stack.pop() {
186+
Number(0) => stack.push(Number(1)),
187+
Number(_) => stack.push(Number(0)),
188+
_ => return Err(~"non-number on stack with logical not")
189+
}
190+
} else { return Err(~"stack is empty") },
191+
'~' => if stack.len() > 0 {
192+
match stack.pop() {
193+
Number(x) => stack.push(Number(!x)),
194+
_ => return Err(~"non-number on stack with %~")
195+
}
196+
} else { return Err(~"stack is empty") },
170197
'i' => match (copy mparams[0], copy mparams[1]) {
171198
(Number(ref mut x), Number(ref mut y)) => {
172199
*x += 1;
@@ -180,15 +207,22 @@ pub fn expand(cap: &[u8], params: &[Param], vars: &mut Variables)
180207
},
181208
PushParam => {
182209
// params are 1-indexed
183-
stack.push(copy mparams[char::to_digit(cur, 10).expect("bad param number") - 1]);
210+
stack.push(copy mparams[match char::to_digit(cur, 10) {
211+
Some(d) => d - 1,
212+
None => return Err(~"bad param number")
213+
}]);
184214
},
185215
SetVar => {
186216
if cur >= 'A' && cur <= 'Z' {
187-
let idx = (cur as u8) - ('A' as u8);
188-
vars.sta[idx] = stack.pop();
217+
if stack.len() > 0 {
218+
let idx = (cur as u8) - ('A' as u8);
219+
vars.sta[idx] = stack.pop();
220+
} else { return Err(~"stack is empty") }
189221
} else if cur >= 'a' && cur <= 'z' {
190-
let idx = (cur as u8) - ('a' as u8);
191-
vars.dyn[idx] = stack.pop();
222+
if stack.len() > 0 {
223+
let idx = (cur as u8) - ('a' as u8);
224+
vars.dyn[idx] = stack.pop();
225+
} else { return Err(~"stack is empty") }
192226
} else {
193227
return Err(~"bad variable name in %P");
194228
}
@@ -205,7 +239,7 @@ pub fn expand(cap: &[u8], params: &[Param], vars: &mut Variables)
205239
}
206240
},
207241
CharConstant => {
208-
stack.push(Char(cur));
242+
stack.push(Number(c as int));
209243
state = CharClose;
210244
},
211245
CharClose => {
@@ -215,28 +249,71 @@ pub fn expand(cap: &[u8], params: &[Param], vars: &mut Variables)
215249
},
216250
IntConstant => {
217251
if cur == '}' {
218-
stack.push(Number(int::parse_bytes(intstate, 10).expect("bad int constant")));
252+
stack.push(match int::parse_bytes(intstate, 10) {
253+
Some(n) => Number(n),
254+
None => return Err(~"bad int constant")
255+
});
256+
intstate.clear();
219257
state = Nothing;
258+
} else {
259+
intstate.push(cur as u8);
260+
old_state = Nothing;
220261
}
221-
intstate.push(cur as u8);
222-
old_state = Nothing;
223262
}
224263
_ => return Err(~"unimplemented state")
225264
}
226265
if state == old_state {
227266
state = Nothing;
228267
}
229-
i += 1;
230268
}
231269
Ok(output)
232270
}
233271

234272
#[cfg(test)]
235273
mod test {
236274
use super::*;
275+
237276
#[test]
238277
fn test_basic_setabf() {
239278
let s = bytes!("\\E[48;5;%p1%dm");
240279
assert_eq!(expand(s, [Number(1)], &mut Variables::new()).unwrap(), bytes!("\\E[48;5;1m").to_owned());
241280
}
281+
282+
#[test]
283+
fn test_multiple_int_constants() {
284+
assert_eq!(expand(bytes!("%{1}%{2}%d%d"), [], &mut Variables::new()).unwrap(), bytes!("21").to_owned());
285+
}
286+
287+
#[test]
288+
fn test_param_stack_failure_conditions() {
289+
let mut varstruct = Variables::new();
290+
let vars = &mut varstruct;
291+
let caps = ["%d", "%c", "%s", "%Pa", "%l", "%!", "%~"];
292+
for caps.iter().advance |cap| {
293+
let res = expand(cap.as_bytes(), [], vars);
294+
assert!(res.is_err(),
295+
"Op %s succeeded incorrectly with 0 stack entries", *cap);
296+
let p = if *cap == "%s" || *cap == "%l" { String(~"foo") } else { Number(97) };
297+
let res = expand((bytes!("%p1")).to_owned() + cap.as_bytes(), [p], vars);
298+
assert!(res.is_ok(),
299+
"Op %s failed with 1 stack entry: %s", *cap, res.unwrap_err());
300+
}
301+
let caps = ["%+", "%-", "%*", "%/", "%m", "%&", "%|", "%A", "%O"];
302+
for caps.iter().advance |cap| {
303+
let res = expand(cap.as_bytes(), [], vars);
304+
assert!(res.is_err(),
305+
"Binop %s succeeded incorrectly with 0 stack entries", *cap);
306+
let res = expand((bytes!("%{1}")).to_owned() + cap.as_bytes(), [], vars);
307+
assert!(res.is_err(),
308+
"Binop %s succeeded incorrectly with 1 stack entry", *cap);
309+
let res = expand((bytes!("%{1}%{2}")).to_owned() + cap.as_bytes(), [], vars);
310+
assert!(res.is_ok(),
311+
"Binop %s failed with 2 stack entries: %s", *cap, res.unwrap_err());
312+
}
313+
}
314+
315+
#[test]
316+
fn test_push_bad_param() {
317+
assert!(expand(bytes!("%pa"), [], &mut Variables::new()).is_err());
318+
}
242319
}

0 commit comments

Comments
 (0)