diff --git a/naga/src/error.rs b/naga/src/error.rs index 3c2f476c9b..d069e7b308 100644 --- a/naga/src/error.rs +++ b/naga/src/error.rs @@ -1,4 +1,4 @@ -use alloc::{boxed::Box, string::String}; +use alloc::{borrow::Cow, boxed::Box, string::String}; use core::{error::Error, fmt}; #[derive(Clone, Debug)] @@ -41,7 +41,7 @@ impl fmt::Display for ShaderError use codespan_reporting::{files::SimpleFile, term}; let label = self.label.as_deref().unwrap_or_default(); - let files = SimpleFile::new(label, &self.source); + let files = SimpleFile::new(label, replace_control_chars(&self.source)); let config = term::Config::default(); let writer = { @@ -137,3 +137,29 @@ where Some(&self.inner) } } + +pub(crate) fn replace_control_chars(s: &str) -> Cow<'_, str> { + const REPLACEMENT_CHAR: &str = "\u{FFFD}"; + + let mut res = Cow::Borrowed(s); + let mut base = 0; + + while let Some(found_pos) = res[base..].find(|c: char| c.is_control() && !c.is_whitespace()) { + let found_len = res[base + found_pos..].chars().next().unwrap().len_utf8(); + res.to_mut().replace_range( + base + found_pos..base + found_pos + found_len, + REPLACEMENT_CHAR, + ); + base += found_pos + REPLACEMENT_CHAR.len(); + } + + res +} + +#[test] +fn test_replace_control_chars() { + // The UTF-8 encoding of \u{0080} is multiple bytes. + let input = "Foo\u{0080}Bar\u{0001}Baz\n"; + let expected = "Foo\u{FFFD}Bar\u{FFFD}Baz\n"; + assert_eq!(replace_control_chars(input), expected); +} diff --git a/naga/src/front/glsl/error.rs b/naga/src/front/glsl/error.rs index 900ad52359..1fb15ef039 100644 --- a/naga/src/front/glsl/error.rs +++ b/naga/src/front/glsl/error.rs @@ -12,7 +12,7 @@ use pp_rs::token::PreprocessorError; use thiserror::Error; use super::token::TokenValue; -use crate::SourceLocation; +use crate::{error::replace_control_chars, SourceLocation}; use crate::{error::ErrorWrite, proc::ConstantEvaluatorError, Span}; fn join_with_comma(list: &[ExpectedToken]) -> String { @@ -171,7 +171,7 @@ impl ParseErrors { pub fn emit_to_writer_with_path(&self, writer: &mut impl ErrorWrite, source: &str, path: &str) { let path = path.to_string(); - let files = SimpleFile::new(path, source); + let files = SimpleFile::new(path, replace_control_chars(source)); let config = term::Config::default(); for err in &self.errors { diff --git a/naga/src/front/spv/error.rs b/naga/src/front/spv/error.rs index acfda0a22f..cf456a9db9 100644 --- a/naga/src/front/spv/error.rs +++ b/naga/src/front/spv/error.rs @@ -8,7 +8,11 @@ use codespan_reporting::files::SimpleFile; use codespan_reporting::term; use super::ModuleState; -use crate::{arena::Handle, error::ErrorWrite, front::atomic_upgrade}; +use crate::{ + arena::Handle, + error::{replace_control_chars, ErrorWrite}, + front::atomic_upgrade, +}; #[derive(Clone, Debug, thiserror::Error)] pub enum Error { @@ -157,7 +161,7 @@ impl Error { pub fn emit_to_writer_with_path(&self, writer: &mut impl ErrorWrite, source: &str, path: &str) { let path = path.to_string(); - let files = SimpleFile::new(path, source); + let files = SimpleFile::new(path, replace_control_chars(source)); let config = term::Config::default(); let diagnostic = Diagnostic::error().with_message(format!("{self:?}")); diff --git a/naga/src/front/wgsl/error.rs b/naga/src/front/wgsl/error.rs index 7101eb6fc9..1cdf53f37d 100644 --- a/naga/src/front/wgsl/error.rs +++ b/naga/src/front/wgsl/error.rs @@ -2,6 +2,7 @@ use crate::common::wgsl::TryToWgsl; use crate::diagnostic_filter::ConflictingDiagnosticRuleError; +use crate::error::replace_control_chars; use crate::proc::{Alignment, ConstantEvaluatorError, ResolveError}; use crate::{Scalar, SourceLocation, Span}; @@ -79,7 +80,7 @@ impl ParseError { P: AsRef, { let path = path.as_ref().display().to_string(); - let files = SimpleFile::new(path, source); + let files = SimpleFile::new(path, replace_control_chars(source)); let config = term::Config::default(); cfg_if::cfg_if! { @@ -105,7 +106,7 @@ impl ParseError { P: AsRef, { let path = path.as_ref().display().to_string(); - let files = SimpleFile::new(path, source); + let files = SimpleFile::new(path, replace_control_chars(source)); let config = term::Config::default(); let mut writer = crate::error::DiagnosticBuffer::new(); diff --git a/naga/src/span.rs b/naga/src/span.rs index 942846e46b..357c2379f2 100644 --- a/naga/src/span.rs +++ b/naga/src/span.rs @@ -6,7 +6,7 @@ use alloc::{ }; use core::{error::Error, fmt, ops::Range}; -use crate::{path_like::PathLike, Arena, Handle, UniqueArena}; +use crate::{error::replace_control_chars, path_like::PathLike, Arena, Handle, UniqueArena}; /// A source code span, used for error reporting. #[derive(Clone, Copy, Debug, PartialEq, Default)] @@ -291,7 +291,7 @@ impl WithSpan { use codespan_reporting::{files, term}; let path = path.to_string_lossy(); - let files = files::SimpleFile::new(path, source); + let files = files::SimpleFile::new(path, replace_control_chars(source)); let config = term::Config::default(); cfg_if::cfg_if! { @@ -323,7 +323,7 @@ impl WithSpan { use codespan_reporting::{files, term}; let path = path.to_string_lossy(); - let files = files::SimpleFile::new(path, source); + let files = files::SimpleFile::new(path, replace_control_chars(source)); let config = term::Config::default(); let mut writer = crate::error::DiagnosticBuffer::new(); diff --git a/naga/tests/naga/wgsl_errors.rs b/naga/tests/naga/wgsl_errors.rs index 79ffe2a49f..adb60a1848 100644 --- a/naga/tests/naga/wgsl_errors.rs +++ b/naga/tests/naga/wgsl_errors.rs @@ -3887,3 +3887,18 @@ fn max_type_size_array_of_structs() { )) } } + +#[cfg(feature = "wgsl-in")] +#[test] +fn source_with_control_char() { + check( + "\x07", + "error: expected global item (`struct`, `const`, `var`, `alias`, `fn`, `diagnostic`, `enable`, `requires`, `;`) or the end of the file, found \"\\u{7}\" + ┌─ wgsl:1:1 + │ +1 │ � + │ ^ expected global item (`struct`, `const`, `var`, `alias`, `fn`, `diagnostic`, `enable`, `requires`, `;`) or the end of the file + +", + ); +}