Skip to content

Commit 0cc8c11

Browse files
Escape control chars in source when printing diagnostics (#8049)
1 parent f9df2b5 commit 0cc8c11

File tree

7 files changed

+61
-13
lines changed

7 files changed

+61
-13
lines changed

CHANGELOG.md

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,7 @@ By @Vecvec in [#7913](https://github.com/gfx-rs/wgpu/pull/7913).
7676
#### Naga
7777

7878
- Naga now requires that no type be larger than 1 GB. This limit may be lowered in the future; feedback on an appropriate value for the limit is welcome. By @andyleiserson in [#7950](https://github.com/gfx-rs/wgpu/pull/7950).
79+
- If the shader source contains control characters, Naga now replaces them with U+FFFD ("replacement character") in diagnostic output. By @andyleiserson in [#8049](https://github.com/gfx-rs/wgpu/pull/8049).
7980

8081
#### DX12
8182

@@ -182,8 +183,6 @@ let (device, queue) = adapter
182183
.unwrap();
183184
```
184185

185-
More examples of this
186-
187186
By @Vecvec in [#7829](https://github.com/gfx-rs/wgpu/pull/7829).
188187

189188
### Naga

naga/src/error.rs

Lines changed: 31 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use alloc::{boxed::Box, string::String};
1+
use alloc::{borrow::Cow, boxed::Box, string::String};
22
use core::{error::Error, fmt};
33

44
#[derive(Clone, Debug)]
@@ -41,7 +41,7 @@ impl fmt::Display for ShaderError<crate::WithSpan<crate::valid::ValidationError>
4141
use codespan_reporting::{files::SimpleFile, term};
4242

4343
let label = self.label.as_deref().unwrap_or_default();
44-
let files = SimpleFile::new(label, &self.source);
44+
let files = SimpleFile::new(label, replace_control_chars(&self.source));
4545
let config = term::Config::default();
4646

4747
let writer = {
@@ -137,3 +137,32 @@ where
137137
Some(&self.inner)
138138
}
139139
}
140+
141+
pub(crate) fn replace_control_chars(s: &str) -> Cow<'_, str> {
142+
const REPLACEMENT_CHAR: &str = "\u{FFFD}";
143+
debug_assert_eq!(
144+
REPLACEMENT_CHAR.chars().next().unwrap(),
145+
char::REPLACEMENT_CHARACTER
146+
);
147+
148+
let mut res = Cow::Borrowed(s);
149+
let mut offset = 0;
150+
151+
while let Some(found_pos) = res[offset..].find(|c: char| c.is_control() && !c.is_whitespace()) {
152+
offset += found_pos;
153+
let found_len = res[offset..].chars().next().unwrap().len_utf8();
154+
res.to_mut()
155+
.replace_range(offset..offset + found_len, REPLACEMENT_CHAR);
156+
offset += REPLACEMENT_CHAR.len();
157+
}
158+
159+
res
160+
}
161+
162+
#[test]
163+
fn test_replace_control_chars() {
164+
// The UTF-8 encoding of \u{0080} is multiple bytes.
165+
let input = "Foo\u{0080}Bar\u{0001}Baz\n";
166+
let expected = "Foo\u{FFFD}Bar\u{FFFD}Baz\n";
167+
assert_eq!(replace_control_chars(input), expected);
168+
}

naga/src/front/glsl/error.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ use pp_rs::token::PreprocessorError;
1212
use thiserror::Error;
1313

1414
use super::token::TokenValue;
15-
use crate::SourceLocation;
15+
use crate::{error::replace_control_chars, SourceLocation};
1616
use crate::{error::ErrorWrite, proc::ConstantEvaluatorError, Span};
1717

1818
fn join_with_comma(list: &[ExpectedToken]) -> String {
@@ -171,7 +171,7 @@ impl ParseErrors {
171171

172172
pub fn emit_to_writer_with_path(&self, writer: &mut impl ErrorWrite, source: &str, path: &str) {
173173
let path = path.to_string();
174-
let files = SimpleFile::new(path, source);
174+
let files = SimpleFile::new(path, replace_control_chars(source));
175175
let config = term::Config::default();
176176

177177
for err in &self.errors {

naga/src/front/spv/error.rs

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,11 @@ use codespan_reporting::files::SimpleFile;
88
use codespan_reporting::term;
99

1010
use super::ModuleState;
11-
use crate::{arena::Handle, error::ErrorWrite, front::atomic_upgrade};
11+
use crate::{
12+
arena::Handle,
13+
error::{replace_control_chars, ErrorWrite},
14+
front::atomic_upgrade,
15+
};
1216

1317
#[derive(Clone, Debug, thiserror::Error)]
1418
pub enum Error {
@@ -157,7 +161,7 @@ impl Error {
157161

158162
pub fn emit_to_writer_with_path(&self, writer: &mut impl ErrorWrite, source: &str, path: &str) {
159163
let path = path.to_string();
160-
let files = SimpleFile::new(path, source);
164+
let files = SimpleFile::new(path, replace_control_chars(source));
161165
let config = term::Config::default();
162166
let diagnostic = Diagnostic::error().with_message(format!("{self:?}"));
163167

naga/src/front/wgsl/error.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
33
use crate::common::wgsl::TryToWgsl;
44
use crate::diagnostic_filter::ConflictingDiagnosticRuleError;
5+
use crate::error::replace_control_chars;
56
use crate::proc::{Alignment, ConstantEvaluatorError, ResolveError};
67
use crate::{Scalar, SourceLocation, Span};
78

@@ -79,7 +80,7 @@ impl ParseError {
7980
P: AsRef<std::path::Path>,
8081
{
8182
let path = path.as_ref().display().to_string();
82-
let files = SimpleFile::new(path, source);
83+
let files = SimpleFile::new(path, replace_control_chars(source));
8384
let config = term::Config::default();
8485

8586
cfg_if::cfg_if! {
@@ -105,7 +106,7 @@ impl ParseError {
105106
P: AsRef<std::path::Path>,
106107
{
107108
let path = path.as_ref().display().to_string();
108-
let files = SimpleFile::new(path, source);
109+
let files = SimpleFile::new(path, replace_control_chars(source));
109110
let config = term::Config::default();
110111

111112
let mut writer = crate::error::DiagnosticBuffer::new();

naga/src/span.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ use alloc::{
66
};
77
use core::{error::Error, fmt, ops::Range};
88

9-
use crate::{path_like::PathLike, Arena, Handle, UniqueArena};
9+
use crate::{error::replace_control_chars, path_like::PathLike, Arena, Handle, UniqueArena};
1010

1111
/// A source code span, used for error reporting.
1212
#[derive(Clone, Copy, Debug, PartialEq, Default)]
@@ -291,7 +291,7 @@ impl<E> WithSpan<E> {
291291
use codespan_reporting::{files, term};
292292

293293
let path = path.to_string_lossy();
294-
let files = files::SimpleFile::new(path, source);
294+
let files = files::SimpleFile::new(path, replace_control_chars(source));
295295
let config = term::Config::default();
296296

297297
cfg_if::cfg_if! {
@@ -323,7 +323,7 @@ impl<E> WithSpan<E> {
323323
use codespan_reporting::{files, term};
324324

325325
let path = path.to_string_lossy();
326-
let files = files::SimpleFile::new(path, source);
326+
let files = files::SimpleFile::new(path, replace_control_chars(source));
327327
let config = term::Config::default();
328328

329329
let mut writer = crate::error::DiagnosticBuffer::new();

naga/tests/naga/wgsl_errors.rs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3887,3 +3887,18 @@ fn max_type_size_array_of_structs() {
38873887
))
38883888
}
38893889
}
3890+
3891+
#[cfg(feature = "wgsl-in")]
3892+
#[test]
3893+
fn source_with_control_char() {
3894+
check(
3895+
"\x07",
3896+
"error: expected global item (`struct`, `const`, `var`, `alias`, `fn`, `diagnostic`, `enable`, `requires`, `;`) or the end of the file, found \"\\u{7}\"
3897+
┌─ wgsl:1:1
3898+
3899+
1 │ �
3900+
│ ^ expected global item (`struct`, `const`, `var`, `alias`, `fn`, `diagnostic`, `enable`, `requires`, `;`) or the end of the file
3901+
3902+
",
3903+
);
3904+
}

0 commit comments

Comments
 (0)