parser: understand nested block comments in macro calls

Fixes <https://issues.oss-fuzz.com/issues/427825995>.
This commit is contained in:
René Kijewski 2025-06-29 09:05:40 +02:00
parent 7610b374db
commit c71aead21b
3 changed files with 53 additions and 11 deletions

View File

@ -7,7 +7,7 @@ use winnow::combinator::{
alt, cut_err, empty, fail, not, opt, peek, preceded, repeat, separated, terminated,
};
use winnow::error::{ErrMode, ParserError as _};
use winnow::token::take_until;
use winnow::token::{one_of, take_until};
use crate::node::CondTest;
use crate::{
@ -839,21 +839,31 @@ impl<'a> Suffix<'a> {
fn block_comment<'a>(i: &mut &'a str) -> ParseResult<'a, ()> {
let start = "/*".parse_next(i)?;
let is_doc_comment = alt((
('*', not(peek('*'))).value(true),
('*', not(peek(one_of(['*', '/'])))).value(true),
'!'.value(true),
empty.value(false),
))
.parse_next(i)?;
if opt(take_until(.., "*/")).parse_next(i)?.is_none() {
return cut_error!(
format!(
"missing `*/` to close block {}comment",
if is_doc_comment { "doc " } else { "" }
),
start,
);
let mut depth = 0usize;
loop {
if opt(take_until(.., ("/*", "*/"))).parse_next(i)?.is_none() {
return cut_error!(
format!(
"missing `*/` to close block {}comment",
if is_doc_comment { "doc " } else { "" }
),
start,
);
} else if alt(("/*".value(true), "*/".value(false))).parse_next(i)? {
// cannot overflow: `i` cannot be longer than `isize::MAX`, cf. [std::alloc::Layout]
depth += 1;
} else if let Some(new_depth) = depth.checked_sub(1) {
depth = new_depth;
} else {
return Ok(());
}
}
Ok(())
}
fn identifier_or_prefixed_string<'a>(i: &mut &'a str) -> ParseResult<'a, ()> {

View File

@ -1561,3 +1561,34 @@ fn test_raw() {
}))],
);
}
#[test]
fn test_macro_call_nested_comments() {
// Regression test for <https://issues.oss-fuzz.com/issues/427825995>.
let syntax = Syntax::default();
assert_eq!(
Ast::from_str("{{ x!(/*/*/*)*/*/*/) }}", None, &syntax)
.unwrap()
.nodes,
vec![Node::Expr(
Ws(None, None),
WithSpan::no_span(Expr::RustMacro(vec!["x"], "/*/*/*)*/*/*/")),
)],
);
let msg = Ast::from_str("{{ x!(/*/*/) }}", None, &syntax)
.unwrap_err()
.to_string();
assert!(msg.contains("missing `*/` to close block comment"));
assert_eq!(
Ast::from_str("{{ x!(/**/) }}", None, &syntax)
.unwrap()
.nodes,
vec![Node::Expr(
Ws(None, None),
WithSpan::no_span(Expr::RustMacro(vec!["x"], "/**/")),
)],
);
}

View File

@ -0,0 +1 @@
<EFBFBD><EFBFBD><EFBFBD>{{K!(/*/*/)}}<7D><><EFBFBD><EFBFBD>u<EFBFBD><75>