Skip to content

Commit b21a326

Browse files
fix(wgsl-in)!: limit brace recursion
1 parent d4b673c commit b21a326

File tree

4 files changed

+194
-18
lines changed

4 files changed

+194
-18
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,7 @@ Bottom level categories:
155155
- In spv-in, remove unnecessary "gl_PerVertex" name check so unused builtins will always be skipped. By @Imberflur in [#5227](https://github.com/gfx-rs/wgpu/pull/5227).
156156
- GLSL 410 does not support layout(binding = ...), enable only for GLSL 420. By @bes in [#5357](https://github.com/gfx-rs/wgpu/pull/5357)
157157
- In spv-out, check for acceleration and ray-query types when enabling ray-query extension to prevent validation error. By @Vecvec in [#5463](https://github.com/gfx-rs/wgpu/pull/5463)
158+
- Add a limit for curly brace nesting in WGSL parsing. By @ErichDonGubler in [#5447](https://github.com/gfx-rs/wgpu/pull/5447).
158159

159160
#### Tests
160161

naga/src/front/wgsl/error.rs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -269,6 +269,10 @@ pub enum Error<'a> {
269269
scalar: String,
270270
inner: ConstantEvaluatorError,
271271
},
272+
ExceededLimitForNestedBraces {
273+
span: Span,
274+
limit: u8,
275+
},
272276
}
273277

274278
impl<'a> Error<'a> {
@@ -770,6 +774,13 @@ impl<'a> Error<'a> {
770774
format!("the expression should have been converted to have {} scalar type", scalar),
771775
]
772776
},
777+
Error::ExceededLimitForNestedBraces { span, limit } => ParseError {
778+
message: "brace nesting limit reached".into(),
779+
labels: vec![(span, "limit reached at this brace".into())],
780+
notes: vec![
781+
format!("nesting limit is currently set to {limit}"),
782+
],
783+
},
773784
}
774785
}
775786
}

naga/src/front/wgsl/parse/mod.rs

Lines changed: 56 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1619,6 +1619,7 @@ impl Parser {
16191619
lexer: &mut Lexer<'a>,
16201620
ctx: &mut ExpressionContext<'a, '_, '_>,
16211621
block: &mut ast::Block<'a>,
1622+
brace_nesting_level: u8,
16221623
) -> Result<(), Error<'a>> {
16231624
self.push_rule_span(Rule::Statement, lexer);
16241625
match lexer.peek() {
@@ -1627,7 +1628,7 @@ impl Parser {
16271628
self.pop_rule_span(lexer);
16281629
}
16291630
(Token::Paren('{'), _) => {
1630-
let (inner, span) = self.block(lexer, ctx)?;
1631+
let (inner, span) = self.block(lexer, ctx, brace_nesting_level)?;
16311632
block.stmts.push(ast::Statement {
16321633
kind: ast::StatementKind::Block(inner),
16331634
span,
@@ -1709,7 +1710,7 @@ impl Parser {
17091710
let _ = lexer.next();
17101711
let condition = self.general_expression(lexer, ctx)?;
17111712

1712-
let accept = self.block(lexer, ctx)?.0;
1713+
let accept = self.block(lexer, ctx, brace_nesting_level)?.0;
17131714

17141715
let mut elsif_stack = Vec::new();
17151716
let mut elseif_span_start = lexer.start_byte_offset();
@@ -1720,12 +1721,12 @@ impl Parser {
17201721

17211722
if !lexer.skip(Token::Word("if")) {
17221723
// ... else { ... }
1723-
break self.block(lexer, ctx)?.0;
1724+
break self.block(lexer, ctx, brace_nesting_level)?.0;
17241725
}
17251726

17261727
// ... else if (...) { ... }
17271728
let other_condition = self.general_expression(lexer, ctx)?;
1728-
let other_block = self.block(lexer, ctx)?;
1729+
let other_block = self.block(lexer, ctx, brace_nesting_level)?;
17291730
elsif_stack.push((elseif_span_start, other_condition, other_block));
17301731
elseif_span_start = lexer.start_byte_offset();
17311732
};
@@ -1757,7 +1758,9 @@ impl Parser {
17571758
"switch" => {
17581759
let _ = lexer.next();
17591760
let selector = self.general_expression(lexer, ctx)?;
1760-
lexer.expect(Token::Paren('{'))?;
1761+
let brace_span = lexer.expect_span(Token::Paren('{'))?;
1762+
let brace_nesting_level =
1763+
Self::increase_brace_nesting(brace_nesting_level, brace_span)?;
17611764
let mut cases = Vec::new();
17621765

17631766
loop {
@@ -1782,7 +1785,7 @@ impl Parser {
17821785
});
17831786
};
17841787

1785-
let body = self.block(lexer, ctx)?.0;
1788+
let body = self.block(lexer, ctx, brace_nesting_level)?.0;
17861789

17871790
cases.push(ast::SwitchCase {
17881791
value,
@@ -1792,7 +1795,7 @@ impl Parser {
17921795
}
17931796
(Token::Word("default"), _) => {
17941797
lexer.skip(Token::Separator(':'));
1795-
let body = self.block(lexer, ctx)?.0;
1798+
let body = self.block(lexer, ctx, brace_nesting_level)?.0;
17961799
cases.push(ast::SwitchCase {
17971800
value: ast::SwitchValue::Default,
17981801
body,
@@ -1808,7 +1811,7 @@ impl Parser {
18081811

18091812
ast::StatementKind::Switch { selector, cases }
18101813
}
1811-
"loop" => self.r#loop(lexer, ctx)?,
1814+
"loop" => self.r#loop(lexer, ctx, brace_nesting_level)?,
18121815
"while" => {
18131816
let _ = lexer.next();
18141817
let mut body = ast::Block::default();
@@ -1832,7 +1835,7 @@ impl Parser {
18321835
span,
18331836
});
18341837

1835-
let (block, span) = self.block(lexer, ctx)?;
1838+
let (block, span) = self.block(lexer, ctx, brace_nesting_level)?;
18361839
body.stmts.push(ast::Statement {
18371840
kind: ast::StatementKind::Block(block),
18381841
span,
@@ -1855,7 +1858,9 @@ impl Parser {
18551858
let (_, span) = {
18561859
let ctx = &mut *ctx;
18571860
let block = &mut *block;
1858-
lexer.capture_span(|lexer| self.statement(lexer, ctx, block))?
1861+
lexer.capture_span(|lexer| {
1862+
self.statement(lexer, ctx, block, brace_nesting_level)
1863+
})?
18591864
};
18601865

18611866
if block.stmts.len() != num_statements {
@@ -1900,7 +1905,7 @@ impl Parser {
19001905
lexer.expect(Token::Paren(')'))?;
19011906
}
19021907

1903-
let (block, span) = self.block(lexer, ctx)?;
1908+
let (block, span) = self.block(lexer, ctx, brace_nesting_level)?;
19041909
body.stmts.push(ast::Statement {
19051910
kind: ast::StatementKind::Block(block),
19061911
span,
@@ -1962,13 +1967,15 @@ impl Parser {
19621967
&mut self,
19631968
lexer: &mut Lexer<'a>,
19641969
ctx: &mut ExpressionContext<'a, '_, '_>,
1970+
brace_nesting_level: u8,
19651971
) -> Result<ast::StatementKind<'a>, Error<'a>> {
19661972
let _ = lexer.next();
19671973
let mut body = ast::Block::default();
19681974
let mut continuing = ast::Block::default();
19691975
let mut break_if = None;
19701976

1971-
lexer.expect(Token::Paren('{'))?;
1977+
let brace_span = lexer.expect_span(Token::Paren('{'))?;
1978+
let brace_nesting_level = Self::increase_brace_nesting(brace_nesting_level, brace_span)?;
19721979

19731980
ctx.local_table.push_scope();
19741981

@@ -1978,7 +1985,9 @@ impl Parser {
19781985
// the last thing in the loop body
19791986

19801987
// Expect a opening brace to start the continuing block
1981-
lexer.expect(Token::Paren('{'))?;
1988+
let brace_span = lexer.expect_span(Token::Paren('{'))?;
1989+
let brace_nesting_level =
1990+
Self::increase_brace_nesting(brace_nesting_level, brace_span)?;
19821991
loop {
19831992
if lexer.skip(Token::Word("break")) {
19841993
// Branch for the `break if` statement, this statement
@@ -2007,7 +2016,7 @@ impl Parser {
20072016
break;
20082017
} else {
20092018
// Otherwise try to parse a statement
2010-
self.statement(lexer, ctx, &mut continuing)?;
2019+
self.statement(lexer, ctx, &mut continuing, brace_nesting_level)?;
20112020
}
20122021
}
20132022
// Since the continuing block must be the last part of the loop body,
@@ -2021,7 +2030,7 @@ impl Parser {
20212030
break;
20222031
}
20232032
// Otherwise try to parse a statement
2024-
self.statement(lexer, ctx, &mut body)?;
2033+
self.statement(lexer, ctx, &mut body, brace_nesting_level)?;
20252034
}
20262035

20272036
ctx.local_table.pop_scope();
@@ -2038,15 +2047,17 @@ impl Parser {
20382047
&mut self,
20392048
lexer: &mut Lexer<'a>,
20402049
ctx: &mut ExpressionContext<'a, '_, '_>,
2050+
brace_nesting_level: u8,
20412051
) -> Result<(ast::Block<'a>, Span), Error<'a>> {
20422052
self.push_rule_span(Rule::Block, lexer);
20432053

20442054
ctx.local_table.push_scope();
20452055

2046-
lexer.expect(Token::Paren('{'))?;
2056+
let brace_span = lexer.expect_span(Token::Paren('{'))?;
2057+
let brace_nesting_level = Self::increase_brace_nesting(brace_nesting_level, brace_span)?;
20472058
let mut block = ast::Block::default();
20482059
while !lexer.skip(Token::Paren('}')) {
2049-
self.statement(lexer, ctx, &mut block)?;
2060+
self.statement(lexer, ctx, &mut block, brace_nesting_level)?;
20502061
}
20512062

20522063
ctx.local_table.pop_scope();
@@ -2133,9 +2144,10 @@ impl Parser {
21332144

21342145
// do not use `self.block` here, since we must not push a new scope
21352146
lexer.expect(Token::Paren('{'))?;
2147+
let brace_nesting_level = 1;
21362148
let mut body = ast::Block::default();
21372149
while !lexer.skip(Token::Paren('}')) {
2138-
self.statement(lexer, &mut ctx, &mut body)?;
2150+
self.statement(lexer, &mut ctx, &mut body, brace_nesting_level)?;
21392151
}
21402152

21412153
ctx.local_table.pop_scope();
@@ -2345,4 +2357,30 @@ impl Parser {
23452357

23462358
Ok(tu)
23472359
}
2360+
2361+
const fn increase_brace_nesting(
2362+
brace_nesting_level: u8,
2363+
brace_span: Span,
2364+
) -> Result<u8, Error<'static>> {
2365+
// From [spec.](https://gpuweb.github.io/gpuweb/wgsl/#limits):
2366+
//
2367+
// > § 2.4. Limits
2368+
// >
2369+
// > …
2370+
// >
2371+
// > Maximum nesting depth of brace-enclosed statements in a function[:] 127
2372+
//
2373+
// _However_, we choose 64 instead because (a) it avoids stack overflows in CI and
2374+
// (b) we expect the limit to be decreased to 63 based on this conversation in
2375+
// WebGPU CTS upstream:
2376+
// <https://github.com/gpuweb/cts/pull/3389#discussion_r1543742701>
2377+
const BRACE_NESTING_MAXIMUM: u8 = 64;
2378+
if brace_nesting_level + 1 > BRACE_NESTING_MAXIMUM {
2379+
return Err(Error::ExceededLimitForNestedBraces {
2380+
span: brace_span,
2381+
limit: BRACE_NESTING_MAXIMUM,
2382+
});
2383+
}
2384+
Ok(brace_nesting_level + 1)
2385+
}
23482386
}

naga/tests/wgsl_errors.rs

Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2151,3 +2151,129 @@ fn compaction_preserves_spans() {
21512151
panic!("Error message has wrong span:\n\n{err:#?}");
21522152
}
21532153
}
2154+
2155+
#[test]
2156+
fn limit_braced_statement_nesting() {
2157+
let too_many_braces = "fn f() {{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{";
2158+
2159+
let expected_diagnostic = r###"error: brace nesting limit reached
2160+
┌─ wgsl:1:72
2161+
2162+
1 │ fn f() {{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{
2163+
│ ^ limit reached at this brace
2164+
2165+
= note: nesting limit is currently set to 64
2166+
2167+
"###;
2168+
2169+
// In debug builds, we might actually overflow the stack before exercising this error case,
2170+
// depending on the platform and the `RUST_MIN_STACK` env. var. Use a thread with a custom
2171+
// stack size that works on all platforms.
2172+
std::thread::Builder::new()
2173+
.stack_size(1024 * 1024 * 2 /* MB */)
2174+
.spawn(|| check(too_many_braces, expected_diagnostic))
2175+
.unwrap()
2176+
.join()
2177+
.unwrap()
2178+
}
2179+
2180+
#[test]
2181+
fn too_many_unclosed_loops() {
2182+
let too_many_braces = "fn f() {
2183+
loop {
2184+
loop {
2185+
loop {
2186+
loop {
2187+
loop {
2188+
loop {
2189+
loop {
2190+
loop {
2191+
loop {
2192+
loop {
2193+
loop {
2194+
loop {
2195+
loop {
2196+
loop {
2197+
loop {
2198+
loop {
2199+
loop {
2200+
loop {
2201+
loop {
2202+
loop {
2203+
loop {
2204+
loop {
2205+
loop {
2206+
loop {
2207+
loop {
2208+
loop {
2209+
loop {
2210+
loop {
2211+
loop {
2212+
loop {
2213+
loop {
2214+
loop {
2215+
loop {
2216+
loop {
2217+
loop {
2218+
loop {
2219+
loop {
2220+
loop {
2221+
loop {
2222+
loop {
2223+
loop {
2224+
loop {
2225+
loop {
2226+
loop {
2227+
loop {
2228+
loop {
2229+
loop {
2230+
loop {
2231+
loop {
2232+
loop {
2233+
loop {
2234+
loop {
2235+
loop {
2236+
loop {
2237+
loop {
2238+
loop {
2239+
loop {
2240+
loop {
2241+
loop {
2242+
loop {
2243+
loop {
2244+
loop {
2245+
loop {
2246+
loop {
2247+
loop {
2248+
loop {
2249+
loop {
2250+
loop {
2251+
loop {
2252+
loop {
2253+
loop {
2254+
loop {
2255+
loop {
2256+
loop {
2257+
loop {
2258+
";
2259+
2260+
let expected_diagnostic = r###"error: brace nesting limit reached
2261+
┌─ wgsl:65:13
2262+
2263+
65 │ loop {
2264+
│ ^ limit reached at this brace
2265+
2266+
= note: nesting limit is currently set to 64
2267+
2268+
"###;
2269+
2270+
// In debug builds, we might actually overflow the stack before exercising this error case,
2271+
// depending on the platform and the `RUST_MIN_STACK` env. var. Use a thread with a custom
2272+
// stack size that works on all platforms.
2273+
std::thread::Builder::new()
2274+
.stack_size(1024 * 1024 * 2 /* MB */)
2275+
.spawn(|| check(too_many_braces, expected_diagnostic))
2276+
.unwrap()
2277+
.join()
2278+
.unwrap()
2279+
}

0 commit comments

Comments
 (0)