Skip to content

Commit c6bf076

Browse files
fix(naga): Reject unsupported enable extensions (#8913)
Previously we required that the enable extension be declared and that the capability be present if the functionality was used, but allowed the extension to be declared if the functionality was not used.
1 parent a24965f commit c6bf076

File tree

11 files changed

+266
-28
lines changed

11 files changed

+266
-28
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,7 @@ Bottom level categories:
115115

116116
#### naga
117117

118+
- Naga and `wgpu` now reject shaders with an `enable` directive for functionality that is not available, even if that functionality is not used by the shader. By @andyleiserson in [#8913](https://github.com/gfx-rs/wgpu/pull/8913).
118119
- Prevent UB from incorrectly using ray queries on HLSL. By @Vecvec in [#8763](https://github.com/gfx-rs/wgpu/pull/8763).
119120

120121
### deno\_webgpu

naga-cli/src/bin/naga.rs

Lines changed: 48 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,14 @@ struct Args {
144144
/// defines to be passed to the parser (only glsl is supported)
145145
#[argh(option, short = 'D')]
146146
defines: Vec<Defines>,
147+
148+
/// capabilities for parsing and validation.
149+
///
150+
/// Can be a comma-separated list of capability names (e.g.,
151+
/// "shader_float16,dual_source_blending"), a numeric bitflags value (e.g.,
152+
/// "67108864"), the string "none", or the string "all".
153+
#[argh(option, default = "CapabilitiesArg(naga::valid::Capabilities::all())")]
154+
capabilities: CapabilitiesArg,
147155
}
148156

149157
/// Newtype so we can implement [`FromStr`] for `BoundsCheckPolicy`.
@@ -334,6 +342,37 @@ impl FromStr for Defines {
334342
}
335343
}
336344

345+
#[derive(Debug, Clone, Copy)]
346+
struct CapabilitiesArg(naga::valid::Capabilities);
347+
348+
impl FromStr for CapabilitiesArg {
349+
type Err = String;
350+
351+
fn from_str(s: &str) -> Result<Self, Self::Err> {
352+
use naga::valid::Capabilities;
353+
354+
let s = s.to_uppercase();
355+
356+
if s == "NONE" {
357+
Ok(Self(Capabilities::empty()))
358+
} else if s == "ALL" {
359+
Ok(Self(Capabilities::all()))
360+
} else if let Ok(bits) = s.parse::<u64>() {
361+
Capabilities::from_bits(bits)
362+
.map(Self)
363+
.ok_or_else(|| format!("Invalid capabilities bitflags value: {bits}"))
364+
} else {
365+
s.split(',')
366+
.try_fold(Capabilities::empty(), |acc, s| {
367+
Capabilities::from_name(s.trim())
368+
.map(|cap| acc | cap)
369+
.ok_or(format!("Unknown capability {}", s.trim()))
370+
})
371+
.map(Self)
372+
}
373+
}
374+
}
375+
337376
#[derive(Default)]
338377
struct Parameters<'a> {
339378
validation_flags: naga::valid::ValidationFlags,
@@ -350,6 +389,7 @@ struct Parameters<'a> {
350389
input_kind: Option<InputKind>,
351390
shader_stage: Option<ShaderStage>,
352391
defines: FastHashMap<String, String>,
392+
capabilities: naga::valid::Capabilities,
353393

354394
/// We use this copy of `args.compact` to know whether we should pass the
355395
/// entrypoint to `process_overrides`, which will result in removal from
@@ -503,6 +543,7 @@ fn run() -> anyhow::Result<()> {
503543
);
504544

505545
params.compact = args.compact;
546+
params.capabilities = args.capabilities.0;
506547

507548
if args.bulk_validate {
508549
return bulk_validate(&args, &params);
@@ -680,7 +721,12 @@ fn parse_input(input_path: &Path, input: Vec<u8>, params: &Parameters) -> anyhow
680721
},
681722
InputKind::Wgsl => {
682723
let input = String::from_utf8(input)?;
683-
let result = naga::front::wgsl::parse_str(&input);
724+
let options = naga::front::wgsl::Options {
725+
parse_doc_comments: false,
726+
capabilities: params.capabilities,
727+
};
728+
let mut frontend = naga::front::wgsl::Frontend::new_with_options(options);
729+
let result = frontend.parse(&input);
684730
match result {
685731
Ok(v) => Parsed {
686732
module: v,
@@ -959,7 +1005,7 @@ fn bulk_validate(args: &Args, params: &Parameters) -> anyhow::Result<()> {
9591005
};
9601006

9611007
let mut validator =
962-
naga::valid::Validator::new(params.validation_flags, naga::valid::Capabilities::all());
1008+
naga::valid::Validator::new(params.validation_flags, params.capabilities);
9631009
validator.subgroup_stages(naga::valid::ShaderStages::all());
9641010
validator.subgroup_operations(naga::valid::SubgroupOperationSet::all());
9651011

naga-test/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,7 @@ impl From<&WgslInParameters> for naga::front::wgsl::Options {
8484
fn from(value: &WgslInParameters) -> Self {
8585
Self {
8686
parse_doc_comments: value.parse_doc_comments,
87+
capabilities: naga::valid::Capabilities::all(),
8788
}
8889
}
8990
}

naga/src/front/wgsl/error.rs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -387,6 +387,10 @@ pub(crate) enum Error<'a> {
387387
kind: EnableExtension,
388388
span: Span,
389389
},
390+
EnableExtensionNotSupported {
391+
kind: EnableExtension,
392+
span: Span,
393+
},
390394
LanguageExtensionNotYetImplemented {
391395
kind: UnimplementedLanguageExtension,
392396
span: Span,
@@ -1258,6 +1262,17 @@ impl<'a> Error<'a> {
12581262
]
12591263
},
12601264
},
1265+
Error::EnableExtensionNotSupported { kind, span } => ParseError {
1266+
message: format!(
1267+
"the `{}` extension is not supported in the current environment",
1268+
kind.to_ident()
1269+
),
1270+
labels: vec![(
1271+
span,
1272+
"unsupported enable-extension".into(),
1273+
)],
1274+
notes: vec![],
1275+
},
12611276
Error::LanguageExtensionNotYetImplemented { kind, span } => ParseError {
12621277
message: format!(
12631278
"the `{}` language extension is not yet supported",

naga/src/front/wgsl/parse/directive/enable_extension.rs

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -156,6 +156,7 @@ impl EnableExtension {
156156

157157
/// A variant of [`EnableExtension::Implemented`].
158158
#[derive(Clone, Copy, Debug, Hash, Eq, PartialEq)]
159+
#[cfg_attr(test, derive(strum::VariantArray))]
159160
pub enum ImplementedEnableExtension {
160161
/// Enables `f16`/`half` primitive support in all shader languages.
161162
///
@@ -187,6 +188,50 @@ pub enum ImplementedEnableExtension {
187188
WgpuCooperativeMatrix,
188189
}
189190

191+
impl ImplementedEnableExtension {
192+
/// A slice of all variants of [`ImplementedEnableExtension`].
193+
pub const VARIANTS: &'static [Self] = &[
194+
Self::F16,
195+
Self::DualSourceBlending,
196+
Self::ClipDistances,
197+
Self::WgpuMeshShader,
198+
Self::WgpuRayQuery,
199+
Self::WgpuRayQueryVertexReturn,
200+
Self::WgpuRayTracingPipeline,
201+
Self::WgpuCooperativeMatrix,
202+
];
203+
204+
/// Returns slice of all variants of [`ImplementedEnableExtension`].
205+
pub const fn all() -> &'static [Self] {
206+
Self::VARIANTS
207+
}
208+
209+
/// Returns the capability required for this enable extension.
210+
pub const fn capability(self) -> crate::valid::Capabilities {
211+
use crate::valid::Capabilities as C;
212+
match self {
213+
Self::F16 => C::SHADER_FLOAT16,
214+
Self::DualSourceBlending => C::DUAL_SOURCE_BLENDING,
215+
Self::ClipDistances => C::CLIP_DISTANCE,
216+
Self::WgpuMeshShader => C::MESH_SHADER,
217+
Self::WgpuRayQuery => C::RAY_QUERY,
218+
Self::WgpuRayQueryVertexReturn => C::RAY_HIT_VERTEX_POSITION,
219+
Self::WgpuCooperativeMatrix => C::COOPERATIVE_MATRIX,
220+
Self::WgpuRayTracingPipeline => C::RAY_TRACING_PIPELINE,
221+
}
222+
}
223+
}
224+
225+
#[test]
226+
/// Asserts that the manual implementation of VARIANTS is the same as the derived strum version would be
227+
/// while still allowing strum to be a dev-only dependency
228+
fn test_manual_variants_array_is_correct() {
229+
assert_eq!(
230+
<ImplementedEnableExtension as strum::VariantArray>::VARIANTS,
231+
ImplementedEnableExtension::VARIANTS
232+
);
233+
}
234+
190235
/// A variant of [`EnableExtension::Unimplemented`].
191236
#[derive(Clone, Copy, Debug, Hash, Eq, PartialEq)]
192237
pub enum UnimplementedEnableExtension {

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

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -275,13 +275,16 @@ impl<'a> BindingParser<'a> {
275275
pub struct Options {
276276
/// Controls whether the parser should parse doc comments.
277277
pub parse_doc_comments: bool,
278+
/// Capabilities to enable during parsing.
279+
pub capabilities: crate::valid::Capabilities,
278280
}
279281

280282
impl Options {
281-
/// Creates a new [`Options`] without doc comments parsing.
283+
/// Creates a new default [`Options`].
282284
pub const fn new() -> Self {
283285
Options {
284286
parse_doc_comments: false,
287+
capabilities: crate::valid::Capabilities::all(),
285288
}
286289
}
287290
}
@@ -2231,6 +2234,14 @@ impl Parser {
22312234
}))
22322235
}
22332236
};
2237+
// Check if the required capability is supported
2238+
let required_capability = extension.capability();
2239+
if !options.capabilities.contains(required_capability) {
2240+
return Err(Box::new(Error::EnableExtensionNotSupported {
2241+
kind,
2242+
span,
2243+
}));
2244+
}
22342245
enable_extensions.add(extension);
22352246
Ok(())
22362247
})?;

naga/tests/naga/wgsl_errors.rs

Lines changed: 44 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313

1414
use naga::{
1515
compact::KeepUnused,
16+
front::wgsl::{EnableExtension, ImplementedEnableExtension},
1617
valid::{self, Capabilities},
1718
};
1819

@@ -4897,7 +4898,7 @@ fn check_ray_tracing_pipeline_payload_disallowed() {
48974898
"enable wgpu_ray_tracing_pipeline;
48984899
@group(0) @binding(0) var acc_struct: acceleration_structure;
48994900
var<ray_payload> payload: u32;
4900-
4901+
49014902
{stage} fn main() {output} {{_ = payload; {stmt}}}"
49024903
),
49034904
Err(naga::valid::ValidationError::EntryPoint {
@@ -4908,3 +4909,45 @@ fn check_ray_tracing_pipeline_payload_disallowed() {
49084909
);
49094910
}
49104911
}
4912+
4913+
#[track_caller]
4914+
fn check_with_capabilities(input: &str, snapshot: &str, capabilities: Capabilities) {
4915+
let mut options = naga::front::wgsl::Options::new();
4916+
options.capabilities = capabilities;
4917+
let mut frontend = naga::front::wgsl::Frontend::new_with_options(options);
4918+
let output = match frontend.parse(input) {
4919+
Ok(_) => panic!("expected parser error, but parsing succeeded!"),
4920+
Err(err) => err.emit_to_string(input),
4921+
};
4922+
if output != snapshot {
4923+
for diff in diff::lines(snapshot, &output) {
4924+
match diff {
4925+
diff::Result::Left(l) => println!("-{l}"),
4926+
diff::Result::Both(l, _) => println!(" {l}"),
4927+
diff::Result::Right(r) => println!("+{r}"),
4928+
}
4929+
}
4930+
panic!("Error snapshot failed");
4931+
}
4932+
}
4933+
4934+
#[test]
4935+
fn enable_without_capability() {
4936+
for extension in ImplementedEnableExtension::all() {
4937+
let ident = EnableExtension::from(*extension).to_ident();
4938+
let carets = "^".repeat(ident.len());
4939+
check_with_capabilities(
4940+
&format!("enable {ident};"),
4941+
&format!(
4942+
"error: the `{ident}` extension is not supported in the current environment
4943+
┌─ wgsl:1:8
4944+
4945+
1 │ enable {ident};
4946+
│ {carets} unsupported enable-extension
4947+
4948+
"
4949+
),
4950+
!extension.capability(),
4951+
);
4952+
}
4953+
}

tests/tests/wgpu-gpu/dual_source_blending.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ fn vs_main() -> @builtin(position) vec4f {
1818
"#;
1919

2020
const FRAGMENT_SHADER_WITHOUT_DUAL_SOURCE_BLENDING: &str = r#"
21-
@fragment
21+
@fragment
2222
fn fs_main() -> @location(0) vec4f {
2323
return vec4f(1.0);
2424
}
@@ -31,7 +31,7 @@ struct FragmentOutput {
3131
@location(0) @blend_src(1) output1_: vec4<f32>,
3232
}
3333
34-
@fragment
34+
@fragment
3535
fn fs_main() -> FragmentOutput {
3636
return FragmentOutput(vec4<f32>(0.4f, 0.3f, 0.2f, 0.1f), vec4<f32>(0.9f, 0.8f, 0.7f, 0.6f));
3737
}
@@ -112,7 +112,7 @@ async fn dual_source_blending_disabled(ctx: TestingContext) {
112112
source: ShaderSource::Wgsl(FRAGMENT_SHADER_WITH_DUAL_SOURCE_BLENDING.into()),
113113
});
114114
},
115-
Some("Capability Capabilities(DUAL_SOURCE_BLENDING) is not supported"),
115+
Some("the `dual_source_blending` extension is not supported in the current environment"),
116116
);
117117
}
118118

tests/tests/wgpu-gpu/shader/compilation_messages/mod.rs

Lines changed: 59 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,14 @@
11
use wgpu::include_wgsl;
22

3-
use wgpu_test::{gpu_test, GpuTestConfiguration, GpuTestInitializer, TestParameters};
3+
use wgpu_test::{fail, gpu_test, valid, GpuTestConfiguration, GpuTestInitializer, TestParameters};
44

55
pub fn all_tests(vec: &mut Vec<GpuTestInitializer>) {
6-
vec.extend([SHADER_COMPILE_SUCCESS, SHADER_COMPILE_ERROR]);
6+
vec.extend([
7+
SHADER_COMPILE_SUCCESS,
8+
SHADER_COMPILE_ERROR,
9+
ENABLE_EXTENSION_AVAILABLE,
10+
ENABLE_EXTENSION_UNAVAILABLE,
11+
]);
712
}
813

914
#[gpu_test]
@@ -51,3 +56,55 @@ static SHADER_COMPILE_ERROR: GpuTestConfiguration = GpuTestConfiguration::new()
5156
"Expected the column number to be 33, because we're counting lines from 1"
5257
);
5358
});
59+
60+
const ENABLE_EXTENSION_SHADER_SOURCE: &str = r#"
61+
enable f16;
62+
63+
@compute @workgroup_size(1)
64+
fn main() {}
65+
"#;
66+
67+
#[gpu_test]
68+
static ENABLE_EXTENSION_AVAILABLE: GpuTestConfiguration = GpuTestConfiguration::new()
69+
.parameters(
70+
TestParameters::default()
71+
.features(wgpu::Features::SHADER_F16)
72+
.downlevel_flags(wgpu::DownlevelFlags::COMPUTE_SHADERS)
73+
.limits(wgpu::Limits::downlevel_defaults()),
74+
)
75+
.run_async(|ctx| async move {
76+
valid(&ctx.device, || {
77+
let _ = ctx
78+
.device
79+
.create_shader_module(wgpu::ShaderModuleDescriptor {
80+
label: Some("shader declaring enable extension"),
81+
source: wgpu::ShaderSource::Wgsl(std::borrow::Cow::Borrowed(
82+
ENABLE_EXTENSION_SHADER_SOURCE,
83+
)),
84+
});
85+
});
86+
});
87+
88+
#[gpu_test]
89+
static ENABLE_EXTENSION_UNAVAILABLE: GpuTestConfiguration = GpuTestConfiguration::new()
90+
.parameters(
91+
TestParameters::default()
92+
// SHADER_F16 feature not requested
93+
.downlevel_flags(wgpu::DownlevelFlags::COMPUTE_SHADERS)
94+
.limits(wgpu::Limits::downlevel_defaults()),
95+
)
96+
.run_async(|ctx| async move {
97+
fail(
98+
&ctx.device,
99+
|| {
100+
ctx.device
101+
.create_shader_module(wgpu::ShaderModuleDescriptor {
102+
label: Some("shader declaring enable extension"),
103+
source: wgpu::ShaderSource::Wgsl(std::borrow::Cow::Borrowed(
104+
ENABLE_EXTENSION_SHADER_SOURCE,
105+
)),
106+
})
107+
},
108+
Some("the `f16` extension is not supported in the current environment"),
109+
);
110+
});

0 commit comments

Comments
 (0)