Comparison operators cannot be chained

This commit is contained in:
René Kijewski 2025-05-22 17:57:35 +02:00 committed by René Kijewski
parent cd13e4f148
commit eaccbb02ae
7 changed files with 156 additions and 34 deletions

View File

@ -1225,3 +1225,17 @@ fn fuzzed_0b85() -> Result<(), syn::Error> {
let _: syn::File = syn::parse2(output)?;
Ok(())
}
#[test]
fn fuzzed_comparator_chain() -> Result<(), syn::Error> {
let input = quote! {
#[template(
ext = "",
source = "\u{c}{{vu7218/63e3666663-666/3330e633/63e3666663666/3333<c\"}\u{1}2}\0\"<c7}}2\"\"\"\"\0\0\0\0"
)]
enum fff {}
};
let output = crate::derive_template(input, import_askama);
let _: syn::File = syn::parse2(output)?;
Ok(())
}

View File

@ -280,7 +280,33 @@ impl<'a> Expr<'a> {
expr_prec_layer!(or, and, "||");
expr_prec_layer!(and, compare, "&&");
expr_prec_layer!(compare, bor, alt(("==", "!=", ">=", ">", "<=", "<",)));
fn compare(i: &mut &'a str, level: Level<'_>) -> ParseResult<'a, WithSpan<'a, Self>> {
let right = |i: &mut _| {
let op = alt(("==", "!=", ">=", ">", "<=", "<"));
(ws(op), |i: &mut _| Self::bor(i, level)).parse_next(i)
};
let start = *i;
let expr = Self::bor(i, level)?;
let Some((op, rhs)) = opt(right).parse_next(i)? else {
return Ok(expr);
};
let expr = WithSpan::new(Self::BinOp(op, Box::new(expr), Box::new(rhs)), start);
if let Some((op2, _)) = opt(right).parse_next(i)? {
return Err(winnow::error::ErrMode::Cut(ErrorContext::new(
format!(
"comparison operators cannot be chained; \
consider using explicit parentheses, e.g. `(_ {op} _) {op2} _`"
),
op,
)));
}
Ok(expr)
}
expr_prec_layer!(bor, bxor, "bitor".value("|"));
expr_prec_layer!(bxor, band, token_xor);
expr_prec_layer!(band, shifts, token_bitand);

View File

@ -605,35 +605,6 @@ fn test_associativity() {
))
)],
);
assert_eq!(
Ast::from_str("{{ a == b != c > d > e == f }}", None, &syntax)
.unwrap()
.nodes,
vec![Node::Expr(
Ws(None, None),
WithSpan::no_span(Expr::BinOp(
"==",
Box::new(WithSpan::no_span(Expr::BinOp(
">",
Box::new(WithSpan::no_span(Expr::BinOp(
">",
Box::new(WithSpan::no_span(Expr::BinOp(
"!=",
Box::new(WithSpan::no_span(Expr::BinOp(
"==",
Box::new(WithSpan::no_span(Expr::Var("a"))),
Box::new(WithSpan::no_span(Expr::Var("b")))
))),
Box::new(WithSpan::no_span(Expr::Var("c")))
))),
Box::new(WithSpan::no_span(Expr::Var("d")))
))),
Box::new(WithSpan::no_span(Expr::Var("e")))
))),
Box::new(WithSpan::no_span(Expr::Var("f")))
))
)],
);
}
#[test]
@ -1423,3 +1394,26 @@ fn there_is_no_digit_two_in_a_binary_integer() {
assert!(Ast::from_str("{{ 0o9 }}", None, &syntax).is_err());
assert!(Ast::from_str("{{ 0xg }}", None, &syntax).is_err());
}
#[test]
fn comparison_operators_cannot_be_chained() {
const OPS: &[&str] = &["==", "!=", ">=", ">", "<=", "<"];
let syntax = Syntax::default();
for op1 in OPS {
assert!(Ast::from_str(&format!("{{{{ a {op1} b }}}}"), None, &syntax).is_ok());
for op2 in OPS {
assert!(Ast::from_str(&format!("{{{{ a {op1} b {op2} c }}}}"), None, &syntax).is_err());
for op3 in OPS {
assert!(
Ast::from_str(
&format!("{{{{ a {op1} b {op2} c {op3} d }}}}"),
None,
&syntax,
)
.is_err()
);
}
}
}
}

View File

@ -0,0 +1,49 @@
// Comparison operators cannot be chained, so our parser must reject chained comparisons.
use askama::Template;
#[derive(Template)]
#[template(ext = "txt", source = "{{ a == b != c }}")]
struct EqNe {
a: usize,
b: usize,
c: usize,
}
#[derive(Template)]
#[template(ext = "txt", source = "{{ a <= b < c }}")]
struct Between {
a: usize,
b: usize,
c: usize,
}
#[derive(Template)]
#[template(ext = "txt", source = "{{ ((a == b) == c) == d == e }}")]
struct ThreeTimesOk {
a: usize,
b: usize,
c: usize,
d: usize,
e: usize,
}
#[derive(Template)]
#[template(ext = "txt", source = "{{ a == (b == (c == d == e)) }}")]
struct ThreeTimesOk2 {
a: usize,
b: usize,
c: usize,
d: usize,
e: usize,
}
// Regression test for <https://github.com/askama-rs/askama/issues/454>
#[derive(Template)]
#[template(
ext = "",
source = "\u{c}{{vu7218/63e3666663-666/3330e633/63e3666663666/3333<c\"}\u{1}2}\0\"<c7}}2\"\"\"\"\0\0\0\0"
)]
struct Regression {}
fn main() {}

View File

@ -0,0 +1,39 @@
error: comparison operators cannot be chained; consider using explicit parentheses, e.g. `(_ == _) != _`
--> <source attribute>:1:5
"== b != c }}"
--> tests/ui/comparator-chaining.rs:6:34
|
6 | #[template(ext = "txt", source = "{{ a == b != c }}")]
| ^^^^^^^^^^^^^^^^^^^
error: comparison operators cannot be chained; consider using explicit parentheses, e.g. `(_ <= _) < _`
--> <source attribute>:1:5
"<= b < c }}"
--> tests/ui/comparator-chaining.rs:14:34
|
14 | #[template(ext = "txt", source = "{{ a <= b < c }}")]
| ^^^^^^^^^^^^^^^^^^
error: comparison operators cannot be chained; consider using explicit parentheses, e.g. `(_ == _) == _`
--> <source attribute>:1:19
"== d == e }}"
--> tests/ui/comparator-chaining.rs:22:34
|
22 | #[template(ext = "txt", source = "{{ ((a == b) == c) == d == e }}")]
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
error: comparison operators cannot be chained; consider using explicit parentheses, e.g. `(_ == _) == _`
--> <source attribute>:1:17
"== d == e)) }}"
--> tests/ui/comparator-chaining.rs:32:34
|
32 | #[template(ext = "txt", source = "{{ a == (b == (c == d == e)) }}")]
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
error: comparison operators cannot be chained; consider using explicit parentheses, e.g. `(_ < _) < _`
--> <source attribute>:1:52
"<c\"}\u{1}2}\0\"<c7}}2\"\"\"\"\0\0\0\0"
--> tests/ui/comparator-chaining.rs:45:14
|
45 | source = "\u{c}{{vu7218/63e3666663-666/3330e633/63e3666663666/3333<c\"}\u{1}2}\0\"<c7}}2\"\"\"\"\0\0\0\0"
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

View File

@ -96,7 +96,7 @@ struct SmallSelf3 {
#[derive(Template)]
#[template(
ext = "",
source = "{{\u{c}KK3e331<c7}}61/63m3333u7<c0.}}\u{6}\0\u{c}\u{c}{{c/crate<338<c7}}6unsafe/63a3ae\u{c}\u{c}\u{c}%et"
source = "{{\u{c}KK3e331<c7}}61/63m3333u7<c0.}}\u{6}\0\u{c}\u{c}{{c/crate<338}}6unsafe/63a3ae\u{c}\u{c}\u{c}%et"
)]
struct Regression {}

View File

@ -168,11 +168,11 @@ error: `self` cannot be used as an identifier
error: `crate` cannot be used as an identifier
--> <source attribute>:1:41
"crate<338<c7}}6unsafe/63a3ae\u{c}\u{c}\u{c}%et"
"crate<338}}6unsafe/63a3ae\u{c}\u{c}\u{c}%et"
--> tests/ui/crate_identifier.rs:99:14
|
99 | source = "{{\u{c}KK3e331<c7}}61/63m3333u7<c0.}}\u{6}\0\u{c}\u{c}{{c/crate<338<c7}}6unsafe/63a3ae\u{c}\u{c}\u{c}%et"
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
99 | source = "{{\u{c}KK3e331<c7}}61/63m3333u7<c0.}}\u{6}\0\u{c}\u{c}{{c/crate<338}}6unsafe/63a3ae\u{c}\u{c}\u{c}%et"
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
error: `super` cannot be used as an identifier
--> <source attribute>:1:27