diff --git a/src/librustc/lint/builtin.rs b/src/librustc/lint/builtin.rs index f8a592d22c19c..42936a24241a8 100644 --- a/src/librustc/lint/builtin.rs +++ b/src/librustc/lint/builtin.rs @@ -427,6 +427,12 @@ pub mod parser { Deny, "trailing content in included file" } + + declare_lint! { + pub NON_PRINTABLE_ASCII, + Warn, + "non-printable ASCII character in a literal" + } } declare_lint! { @@ -522,6 +528,7 @@ declare_lint_pass! { MACRO_EXPANDED_MACRO_EXPORTS_ACCESSED_BY_ABSOLUTE_PATHS, parser::ILL_FORMED_ATTRIBUTE_INPUT, parser::META_VARIABLE_MISUSE, + parser::NON_PRINTABLE_ASCII, DEPRECATED_IN_FUTURE, AMBIGUOUS_ASSOCIATED_ITEMS, MUTABLE_BORROW_RESERVATION_CONFLICT, diff --git a/src/librustc/lint/mod.rs b/src/librustc/lint/mod.rs index 11d0d0d90fa86..98e465d60e09f 100644 --- a/src/librustc/lint/mod.rs +++ b/src/librustc/lint/mod.rs @@ -27,8 +27,8 @@ use crate::hir::def_id::{CrateNum, LOCAL_CRATE}; use crate::hir::intravisit; use crate::hir; use crate::lint::builtin::BuiltinLintDiagnostics; -use crate::lint::builtin::parser::{ILL_FORMED_ATTRIBUTE_INPUT, META_VARIABLE_MISUSE}; -use crate::lint::builtin::parser::INCOMPLETE_INCLUDE; +use crate::lint::builtin::parser::{ILL_FORMED_ATTRIBUTE_INPUT, META_VARIABLE_MISUSE, + INCOMPLETE_INCLUDE, NON_PRINTABLE_ASCII}; use crate::session::{Session, DiagnosticMessageId}; use crate::ty::TyCtxt; use crate::ty::query::Providers; @@ -111,6 +111,7 @@ impl Lint { BufferedEarlyLintId::IllFormedAttributeInput => ILL_FORMED_ATTRIBUTE_INPUT, BufferedEarlyLintId::MetaVariableMisuse => META_VARIABLE_MISUSE, BufferedEarlyLintId::IncompleteInclude => INCOMPLETE_INCLUDE, + BufferedEarlyLintId::NonPrintableAscii => NON_PRINTABLE_ASCII, } } diff --git a/src/librustc_parse/lexer/mod.rs b/src/librustc_parse/lexer/mod.rs index 5de63cb39d16b..8f57810c8cd78 100644 --- a/src/librustc_parse/lexer/mod.rs +++ b/src/librustc_parse/lexer/mod.rs @@ -16,7 +16,8 @@ use log::debug; mod tokentrees; mod unicode_chars; mod unescape_error_reporting; -use unescape_error_reporting::{emit_unescape_error, push_escaped_char}; +use unescape_error_reporting::{emit_unescape_error, push_escaped_char, + lint_unescaped_char, lint_unescaped_byte}; #[derive(Clone, Debug)] pub struct UnmatchedBrace { @@ -532,92 +533,134 @@ impl<'a> StringReader<'a> { fn validate_char_escape(&self, content_start: BytePos, content_end: BytePos) { let lit = self.str_from_to(content_start, content_end); - if let Err((off, err)) = unescape::unescape_char(lit) { - emit_unescape_error( + let span = self.mk_sp(content_start - BytePos(1), content_end + BytePos(1)); + match unescape::unescape_char(lit) { + Ok(c) => lint_unescaped_char( + &self.sess, + span, + c, + 0..lit.len(), + ), + Err((off, err)) => emit_unescape_error( &self.sess.span_diagnostic, lit, - self.mk_sp(content_start - BytePos(1), content_end + BytePos(1)), + span, unescape::Mode::Char, 0..off, err, - ) + ), } } fn validate_byte_escape(&self, content_start: BytePos, content_end: BytePos) { let lit = self.str_from_to(content_start, content_end); - if let Err((off, err)) = unescape::unescape_byte(lit) { - emit_unescape_error( + let span = self.mk_sp(content_start - BytePos(1), content_end + BytePos(1)); + match unescape::unescape_byte(lit) { + Ok(c) => lint_unescaped_byte( + &self.sess, + span, + c, + 0..lit.len(), + ), + Err((off, err)) => emit_unescape_error( &self.sess.span_diagnostic, lit, - self.mk_sp(content_start - BytePos(1), content_end + BytePos(1)), + span, unescape::Mode::Byte, 0..off, err, - ) + ), } } fn validate_str_escape(&self, content_start: BytePos, content_end: BytePos) { let lit = self.str_from_to(content_start, content_end); + let span = self.mk_sp(content_start - BytePos(1), content_end + BytePos(1)); unescape::unescape_str(lit, &mut |range, c| { - if let Err(err) = c { - emit_unescape_error( + match c { + Ok(c) => lint_unescaped_char( + &self.sess, + span, + c, + range, + ), + Err(err) => emit_unescape_error( &self.sess.span_diagnostic, lit, - self.mk_sp(content_start - BytePos(1), content_end + BytePos(1)), + span, unescape::Mode::Str, range, err, - ) + ), } }) } fn validate_raw_str_escape(&self, content_start: BytePos, content_end: BytePos) { let lit = self.str_from_to(content_start, content_end); + let span = self.mk_sp(content_start - BytePos(1), content_end + BytePos(1)); unescape::unescape_raw_str(lit, &mut |range, c| { - if let Err(err) = c { - emit_unescape_error( + match c { + Ok(c) => lint_unescaped_char( + &self.sess, + span, + c, + range, + ), + Err(err) => emit_unescape_error( &self.sess.span_diagnostic, lit, - self.mk_sp(content_start - BytePos(1), content_end + BytePos(1)), + span, unescape::Mode::Str, range, err, - ) + ), } }) } fn validate_raw_byte_str_escape(&self, content_start: BytePos, content_end: BytePos) { let lit = self.str_from_to(content_start, content_end); + let span = self.mk_sp(content_start - BytePos(1), content_end + BytePos(1)); unescape::unescape_raw_byte_str(lit, &mut |range, c| { - if let Err(err) = c { - emit_unescape_error( + match c { + Ok(c) => lint_unescaped_byte( + &self.sess, + span, + c, + range, + ), + Err(err) => emit_unescape_error( &self.sess.span_diagnostic, lit, - self.mk_sp(content_start - BytePos(1), content_end + BytePos(1)), + span, unescape::Mode::ByteStr, range, err, - ) + ), } }) } fn validate_byte_str_escape(&self, content_start: BytePos, content_end: BytePos) { let lit = self.str_from_to(content_start, content_end); + let span = self.mk_sp(content_start - BytePos(1), content_end + BytePos(1)); unescape::unescape_byte_str(lit, &mut |range, c| { - if let Err(err) = c { - emit_unescape_error( + match c { + Ok(c) => lint_unescaped_byte( + &self.sess, + span, + c, + range, + ), + Err(err) => emit_unescape_error( &self.sess.span_diagnostic, lit, - self.mk_sp(content_start - BytePos(1), content_end + BytePos(1)), + span, unescape::Mode::ByteStr, range, err, - ) + ), } }) } diff --git a/src/librustc_parse/lexer/unescape_error_reporting.rs b/src/librustc_parse/lexer/unescape_error_reporting.rs index a5749d07e62b5..ade5754ad37a1 100644 --- a/src/librustc_parse/lexer/unescape_error_reporting.rs +++ b/src/librustc_parse/lexer/unescape_error_reporting.rs @@ -6,7 +6,8 @@ use std::iter::once; use rustc_lexer::unescape::{EscapeError, Mode}; use syntax_pos::{Span, BytePos}; -use syntax::errors::{Handler, Applicability}; +use syntax::{errors::{Handler, Applicability}, sess::ParseSess, + early_buffered_lints::BufferedEarlyLintId, ast}; pub(crate) fn emit_unescape_error( handler: &Handler, @@ -201,6 +202,40 @@ pub(crate) fn emit_unescape_error( } } +pub(crate) fn lint_unescaped_char( + sess: &ParseSess, + // full span of the literal, including quotes + span_with_quotes: Span, + // interior part of the literal, without quotes + character: char, + range: Range, +) { + const ALLOWED_ASCII_CONTROL_CHARS: [char; 2] = ['\t', '\n']; + + if range.len() != 1 { + return; + } + if !ALLOWED_ASCII_CONTROL_CHARS.contains(&character) && character.is_ascii_control() { + sess.buffer_lint( + BufferedEarlyLintId::NonPrintableAscii, + span_with_quotes, + ast::CRATE_NODE_ID, + "non-printable ASCII character in literal" + ) + } +} + +pub(crate) fn lint_unescaped_byte( + sess: &ParseSess, + // full span of the literal, including quotes + span_with_quotes: Span, + // interior part of the literal, without quotes + byte: u8, + range: Range, +) { + lint_unescaped_char(sess, span_with_quotes, char::from(byte), range); +} + /// Pushes a character to a message string for error reporting pub(crate) fn push_escaped_char(msg: &mut String, c: char) { match c { diff --git a/src/libsyntax/early_buffered_lints.rs b/src/libsyntax/early_buffered_lints.rs index 5cc953b906628..c2e87518ba505 100644 --- a/src/libsyntax/early_buffered_lints.rs +++ b/src/libsyntax/early_buffered_lints.rs @@ -12,6 +12,7 @@ pub enum BufferedEarlyLintId { IllFormedAttributeInput, MetaVariableMisuse, IncompleteInclude, + NonPrintableAscii, } /// Stores buffered lint info which can later be passed to `librustc`. diff --git a/src/test/ui/lint/non-printable-string-literals.rs b/src/test/ui/lint/non-printable-string-literals.rs new file mode 100644 index 0000000000000..4d79136a0b535 Binary files /dev/null and b/src/test/ui/lint/non-printable-string-literals.rs differ diff --git a/src/test/ui/lint/non-printable-string-literals.stderr b/src/test/ui/lint/non-printable-string-literals.stderr new file mode 100644 index 0000000000000..5abfa423e8bf1 Binary files /dev/null and b/src/test/ui/lint/non-printable-string-literals.stderr differ diff --git a/src/test/ui/raw-str.rs b/src/test/ui/raw-str.rs index 0916dddbb7be6..efe7753872cea 100644 Binary files a/src/test/ui/raw-str.rs and b/src/test/ui/raw-str.rs differ