Skip to content

Commit bbc1389

Browse files
committed
[naga] Warn, rather than error, on unreachable statements
Fixes #7536
1 parent 09178f2 commit bbc1389

File tree

8 files changed

+343
-73
lines changed

8 files changed

+343
-73
lines changed

naga-cli/src/bin/naga.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ use anyhow::{anyhow, Context as _};
44
use std::fs;
55
use std::{error::Error, fmt, io::Read, path::Path, str::FromStr};
66

7+
use naga::Diagnostic;
8+
79
/// Translate shaders to different formats.
810
#[derive(argh::FromArgs, Debug, Clone)]
911
struct Args {

naga/src/error.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
use alloc::{boxed::Box, string::String, vec::Vec};
22
use core::{error::Error, fmt};
33

4+
use crate::span::Diagnostic;
5+
46
#[derive(Clone, Debug)]
57
pub struct ShaderError<E> {
68
/// The source code of the shader.

naga/src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -134,7 +134,7 @@ pub mod valid;
134134
use alloc::string::String;
135135

136136
pub use crate::arena::{Arena, Handle, Range, UniqueArena};
137-
pub use crate::span::{SourceLocation, Span, SpanContext, WithSpan};
137+
pub use crate::span::{Diagnostic, SourceLocation, Span, SpanContext, WithSpan};
138138

139139
// TODO: Eliminate this re-export and migrate uses of `crate::Foo` to `use crate::ir; ir::Foo`.
140140
pub use ir::*;

naga/src/span.rs

Lines changed: 75 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
use alloc::{
22
borrow::ToOwned,
3+
boxed::Box,
34
format,
45
string::{String, ToString},
56
vec::Vec,
@@ -140,6 +141,20 @@ pub struct WithSpan<E> {
140141
spans: Vec<SpanContext>,
141142
}
142143

144+
/// Variant of [`WithSpan`] that holds a boxed [`Error`].
145+
///
146+
/// This is currently used to hold warnings from validation, and since we don't
147+
/// have severity information in [`WithSpan`] or in the error itself, the
148+
/// implementations of [`Diagnostic`] assume that any [`WithSpan`] is a
149+
/// [`codespan_reporting::diagnostic::Severity::Error`] and any
150+
/// [`WithSpanBoxed`] is a
151+
/// [`codespan_reporting::diagnostic::Severity::Warning`].
152+
#[derive(Debug)]
153+
pub struct WithSpanBoxed {
154+
inner: Box<dyn Error>,
155+
spans: Vec<SpanContext>,
156+
}
157+
143158
impl<E> fmt::Display for WithSpan<E>
144159
where
145160
E: fmt::Display,
@@ -237,6 +252,17 @@ impl<E> WithSpan<E> {
237252
res
238253
}
239254

255+
/// Convert this [`WithSpan`] into a [`WithSpanBoxed`].
256+
pub fn boxed(self) -> WithSpanBoxed
257+
where
258+
E: Error + 'static,
259+
{
260+
WithSpanBoxed {
261+
inner: Box::new(self.inner),
262+
spans: self.spans,
263+
}
264+
}
265+
240266
/// Return a [`SourceLocation`] for our first span, if we have one.
241267
pub fn location(&self, source: &str) -> Option<SourceLocation> {
242268
if self.spans.is_empty() || source.is_empty() {
@@ -245,46 +271,61 @@ impl<E> WithSpan<E> {
245271

246272
Some(self.spans[0].0.location(source))
247273
}
274+
}
248275

249-
pub(crate) fn diagnostic(&self) -> codespan_reporting::diagnostic::Diagnostic<()>
250-
where
251-
E: Error,
252-
{
253-
use codespan_reporting::diagnostic::{Diagnostic, Label};
254-
let diagnostic = Diagnostic::error()
255-
.with_message(self.inner.to_string())
256-
.with_labels(
257-
self.spans()
258-
.map(|&(span, ref desc)| {
259-
Label::primary((), span.to_range().unwrap()).with_message(desc.to_owned())
260-
})
261-
.collect(),
262-
)
263-
.with_notes({
264-
let mut notes = Vec::new();
265-
let mut source: &dyn Error = &self.inner;
266-
while let Some(next) = Error::source(source) {
267-
notes.push(next.to_string());
268-
source = next;
269-
}
270-
notes
271-
});
272-
diagnostic
276+
fn diagnostic(
277+
severity: codespan_reporting::diagnostic::Severity,
278+
error: &dyn Error,
279+
spans: &[SpanContext],
280+
) -> codespan_reporting::diagnostic::Diagnostic<()> {
281+
use codespan_reporting::diagnostic::{Diagnostic, Label};
282+
let diagnostic = Diagnostic::new(severity)
283+
.with_message(error.to_string())
284+
.with_labels(
285+
spans
286+
.iter()
287+
.map(|&(span, ref desc)| {
288+
Label::primary((), span.to_range().unwrap()).with_message(desc.to_owned())
289+
})
290+
.collect(),
291+
)
292+
.with_notes({
293+
let mut notes = Vec::new();
294+
let mut source = error;
295+
while let Some(next) = Error::source(source) {
296+
notes.push(next.to_string());
297+
source = next;
298+
}
299+
notes
300+
});
301+
diagnostic
302+
}
303+
304+
impl<E: Error> Diagnostic for WithSpan<E> {
305+
fn diagnostic(&self) -> codespan_reporting::diagnostic::Diagnostic<()> {
306+
use codespan_reporting::diagnostic::Severity;
307+
diagnostic(Severity::Error, &self.inner, &self.spans)
273308
}
309+
}
310+
311+
impl Diagnostic for WithSpanBoxed {
312+
fn diagnostic(&self) -> codespan_reporting::diagnostic::Diagnostic<()> {
313+
/// We assume that `self` is a warning. See documentation for [`WithSpanBoxed`].
314+
use codespan_reporting::diagnostic::Severity;
315+
diagnostic(Severity::Warning, self.inner.as_ref(), &self.spans)
316+
}
317+
}
318+
319+
pub trait Diagnostic {
320+
fn diagnostic(&self) -> codespan_reporting::diagnostic::Diagnostic<()>;
274321

275322
/// Emits a summary of the error to standard error stream.
276-
pub fn emit_to_stderr(&self, source: &str)
277-
where
278-
E: Error,
279-
{
323+
fn emit_to_stderr(&self, source: &str) {
280324
self.emit_to_stderr_with_path(source, "wgsl")
281325
}
282326

283327
/// Emits a summary of the error to standard error stream.
284-
pub fn emit_to_stderr_with_path(&self, source: &str, path: &str)
285-
where
286-
E: Error,
287-
{
328+
fn emit_to_stderr_with_path(&self, source: &str, path: &str) {
288329
use codespan_reporting::term::termcolor::{ColorChoice, StandardStream};
289330
use codespan_reporting::{files, term};
290331

@@ -296,18 +337,12 @@ impl<E> WithSpan<E> {
296337
}
297338

298339
/// Emits a summary of the error to a string.
299-
pub fn emit_to_string(&self, source: &str) -> String
300-
where
301-
E: Error,
302-
{
340+
fn emit_to_string(&self, source: &str) -> String {
303341
self.emit_to_string_with_path(source, "wgsl")
304342
}
305343

306344
/// Emits a summary of the error to a string.
307-
pub fn emit_to_string_with_path(&self, source: &str, path: &str) -> String
308-
where
309-
E: Error,
310-
{
345+
fn emit_to_string_with_path(&self, source: &str, path: &str) -> String {
311346
use codespan_reporting::term::termcolor::NoColor;
312347
use codespan_reporting::{files, term};
313348

naga/src/valid/function.rs

Lines changed: 43 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ use super::{
77
};
88
use crate::arena::{Arena, UniqueArena};
99
use crate::arena::{Handle, HandleSet};
10+
use crate::diagnostic_filter::Severity;
1011
use crate::proc::TypeResolution;
1112
use crate::span::WithSpan;
1213
use crate::span::{AddSpan as _, MapErrWithSpan as _};
@@ -112,8 +113,8 @@ pub enum FunctionError {
112113
name: String,
113114
space: crate::AddressSpace,
114115
},
115-
#[error("There are instructions after `return`/`break`/`continue`")]
116-
InstructionsAfterReturn,
116+
#[error("Unreachable statement after `{after}`")]
117+
UnreachableStatement { after: &'static str },
117118
#[error("The `break` is used outside of a `loop` or `switch` context")]
118119
BreakOutsideOfLoopOrSwitch,
119120
#[error("The `continue` is used outside of a `loop` context")]
@@ -232,9 +233,18 @@ bitflags::bitflags! {
232233
}
233234
}
234235

236+
/// Return type of `validate_block`
235237
struct BlockInfo {
238+
/// Shader stages for which the block is valid.
236239
stages: super::ShaderStages,
237-
finished: bool,
240+
241+
/// Span for a control flow statement that terminated the block.
242+
///
243+
/// If the block contained a statement like `return`, `break`, `continue`,
244+
/// or `discard` that will cause further statements not to be executed, this
245+
/// is set based on that statement. This is used for "unreachable statement"
246+
/// diagnostics.
247+
unreachable: Option<(&'static str, crate::Span)>,
238248
}
239249

240250
struct BlockContext<'a> {
@@ -751,12 +761,29 @@ impl super::Validator {
751761
context: &BlockContext,
752762
) -> Result<BlockInfo, WithSpan<FunctionError>> {
753763
use crate::{AddressSpace, Statement as S, TypeInner as Ti};
754-
let mut finished = false;
764+
// Information used for "unreachable statement" diagnostics. Upon
765+
// diverging control flow like `return`, this is set to `Some(Some(_))`.
766+
// After emitting a diagnostic, this is set to `Some(None)`, so that
767+
// there is only one warning for each block containing unreachable
768+
// statements.
769+
let mut unreachable = None;
755770
let mut stages = super::ShaderStages::all();
756771
for (statement, &span) in statements.span_iter() {
757-
if finished {
758-
return Err(FunctionError::InstructionsAfterReturn
759-
.with_span_static(span, "instructions after return"));
772+
if let Some(Some((prev_stmt, prev_span))) = unreachable {
773+
Severity::Warning.report_diag(
774+
FunctionError::UnreachableStatement { after: prev_stmt }
775+
.with_span()
776+
.with_context((span, String::from("this statement is unreachable")))
777+
.with_context((
778+
prev_span,
779+
String::from("because it appears after this statement"),
780+
)),
781+
|e, level| {
782+
log::log!(level, "{e}");
783+
self.diagnostics.push(e.boxed());
784+
},
785+
)?;
786+
unreachable = Some(None);
760787
}
761788
match *statement {
762789
S::Emit(ref range) => {
@@ -806,7 +833,7 @@ impl super::Validator {
806833
S::Block(ref block) => {
807834
let info = self.validate_block(block, context)?;
808835
stages &= info.stages;
809-
finished = info.finished;
836+
unreachable.get_or_insert(info.unreachable);
810837
}
811838
S::If {
812839
condition,
@@ -949,14 +976,14 @@ impl super::Validator {
949976
return Err(FunctionError::BreakOutsideOfLoopOrSwitch
950977
.with_span_static(span, "invalid break"));
951978
}
952-
finished = true;
979+
unreachable.get_or_insert(Some(("break", span)));
953980
}
954981
S::Continue => {
955982
if !context.abilities.contains(ControlFlowAbility::CONTINUE) {
956983
return Err(FunctionError::ContinueOutsideOfLoop
957984
.with_span_static(span, "invalid continue"));
958985
}
959-
finished = true;
986+
unreachable.get_or_insert(Some(("continue", span)));
960987
}
961988
S::Return { value } => {
962989
if !context.abilities.contains(ControlFlowAbility::RETURN) {
@@ -996,11 +1023,11 @@ impl super::Validator {
9961023
.with_span_static(span, "invalid return"));
9971024
}
9981025
}
999-
finished = true;
1026+
unreachable.get_or_insert(Some(("return", span)));
10001027
}
10011028
S::Kill => {
10021029
stages &= super::ShaderStages::FRAGMENT;
1003-
finished = true;
1030+
unreachable.get_or_insert(Some(("discard", span)));
10041031
}
10051032
S::Barrier(barrier) => {
10061033
stages &= super::ShaderStages::COMPUTE;
@@ -1618,7 +1645,10 @@ impl super::Validator {
16181645
}
16191646
}
16201647
}
1621-
Ok(BlockInfo { stages, finished })
1648+
Ok(BlockInfo {
1649+
stages,
1650+
unreachable: unreachable.unwrap_or(None),
1651+
})
16221652
}
16231653

16241654
fn validate_block(

naga/src/valid/mod.rs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ use bit_set::BitSet;
1818
use crate::{
1919
arena::{Handle, HandleSet},
2020
proc::{ExpressionKindTracker, LayoutError, Layouter, TypeResolution},
21+
span::WithSpanBoxed,
2122
FastHashSet,
2223
};
2324

@@ -283,6 +284,7 @@ pub struct Validator {
283284
switch_values: FastHashSet<crate::SwitchValue>,
284285
valid_expression_list: Vec<Handle<crate::Expression>>,
285286
valid_expression_set: HandleSet<crate::Expression>,
287+
diagnostics: Vec<WithSpanBoxed>,
286288
override_ids: FastHashSet<u16>,
287289

288290
/// Treat overrides whose initializers are not fully-evaluated
@@ -485,6 +487,7 @@ impl Validator {
485487
switch_values: FastHashSet::default(),
486488
valid_expression_list: Vec::new(),
487489
valid_expression_set: HandleSet::new(),
490+
diagnostics: Vec::new(),
488491
override_ids: FastHashSet::default(),
489492
overrides_resolved: false,
490493
needs_visit: HandleSet::new(),
@@ -511,6 +514,7 @@ impl Validator {
511514
self.switch_values.clear();
512515
self.valid_expression_list.clear();
513516
self.valid_expression_set.clear();
517+
self.diagnostics.clear();
514518
self.override_ids.clear();
515519
}
516520

@@ -758,6 +762,13 @@ impl Validator {
758762

759763
Ok(mod_info)
760764
}
765+
766+
/// Return any warnings produced during validation.
767+
///
768+
/// The details of returned diagnostic messages are not a stable API.
769+
pub fn diagnostics(&self) -> &[WithSpanBoxed] {
770+
&self.diagnostics
771+
}
761772
}
762773

763774
fn validate_atomic_compare_exchange_struct(

0 commit comments

Comments
 (0)