Skip to content

Commit

Permalink
Detect NulInCStr error earlier.
Browse files Browse the repository at this point in the history
By making it an `EscapeError` instead of a `LitError`. This makes it
more like the other errors produced during unescaping.

NOTE: this means these errors are issued earlier, before expansion,
which changes behaviour. The next commit will delay issue of this error
and others, reverting the behaviour change for this particular error.

One nice thing about this: the old approach had some code in
`report_lit_error` to calculate the span of the nul char from a range.
This code used a hardwired `+2` to account for the `c"` at the start of
a C string literal, but this should have changed to a `+3` for raw C
string literals to account for the `cr"`, which meant that the caret in
`cr"` nul error messages was one short of where it should have been. The
new approach doesn't need any of this and avoids the off-by-one error.
  • Loading branch information
nnethercote committed Dec 18, 2023
1 parent 1edec28 commit 25dd832
Show file tree
Hide file tree
Showing 8 changed files with 26 additions and 28 deletions.
12 changes: 2 additions & 10 deletions compiler/rustc_ast/src/util/literal.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ use rustc_lexer::unescape::{
};
use rustc_span::symbol::{kw, sym, Symbol};
use rustc_span::Span;
use std::ops::Range;
use std::{ascii, fmt, str};

// Escapes a string, represented as a symbol. Reuses the original symbol,
Expand Down Expand Up @@ -39,7 +38,6 @@ pub enum LitError {
InvalidFloatSuffix,
NonDecimalFloat(u32),
IntTooLarge(u32),
NulInCStr(Range<usize>),
}

impl LitKind {
Expand Down Expand Up @@ -156,10 +154,7 @@ impl LitKind {
let s = symbol.as_str();
let mut buf = Vec::with_capacity(s.len());
let mut error = Ok(());
unescape_c_string(s, Mode::CStr, &mut |span, c| match c {
Ok(CStrUnit::Byte(0) | CStrUnit::Char('\0')) => {
error = Err(LitError::NulInCStr(span));
}
unescape_c_string(s, Mode::CStr, &mut |_span, c| match c {
Ok(CStrUnit::Byte(b)) => buf.push(b),
Ok(CStrUnit::Char(c)) => {
buf.extend_from_slice(c.encode_utf8(&mut [0; 4]).as_bytes())
Expand All @@ -179,10 +174,7 @@ impl LitKind {
// can convert the symbol directly to a `Lrc<u8>` on success.
let s = symbol.as_str();
let mut error = Ok(());
unescape_c_string(s, Mode::RawCStr, &mut |span, c| match c {
Ok(CStrUnit::Byte(0) | CStrUnit::Char('\0')) => {
error = Err(LitError::NulInCStr(span));
}
unescape_c_string(s, Mode::RawCStr, &mut |_, c| match c {
Ok(_) => {}
Err(err) => {
if err.is_fatal() {
Expand Down
15 changes: 13 additions & 2 deletions compiler/rustc_lexer/src/unescape.rs
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,9 @@ pub enum EscapeError {
/// Non-ascii character in byte literal, byte string literal, or raw byte string literal.
NonAsciiCharInByte,

// `\0` in a C string literal.
NulInCStr,

/// After a line ending with '\', the next line contains whitespace
/// characters that are not skipped.
UnskippedWhitespaceWarning,
Expand Down Expand Up @@ -122,10 +125,18 @@ where
{
match mode {
CStr => {
unescape_str_common(src, mode, callback);
unescape_str_common(src, mode, &mut |r, mut result| {
if let Ok(CStrUnit::Byte(0) | CStrUnit::Char('\0')) = result {
result = Err(EscapeError::NulInCStr);
}
callback(r, result)
});
}
RawCStr => {
unescape_raw_str_or_raw_byte_str(src, mode, &mut |r, result| {
unescape_raw_str_or_raw_byte_str(src, mode, &mut |r, mut result| {
if let Ok('\0') = result {
result = Err(EscapeError::NulInCStr);
}
callback(r, result.map(CStrUnit::Char))
});
}
Expand Down
2 changes: 2 additions & 0 deletions compiler/rustc_parse/messages.ftl
Original file line number Diff line number Diff line change
Expand Up @@ -612,6 +612,8 @@ parse_note_mut_pattern_usage = `mut` may be followed by `variable` and `variable
parse_note_pattern_alternatives_use_single_vert = alternatives in or-patterns are separated with `|`, not `||`
parse_nul_in_c_str = null characters in C string literals are not supported
parse_or_pattern_not_allowed_in_fn_parameters = top-level or-patterns are not allowed in function parameters
parse_or_pattern_not_allowed_in_let_binding = top-level or-patterns are not allowed in `let` bindings
parse_out_of_range_hex_escape = out of range hex escape
Expand Down
5 changes: 5 additions & 0 deletions compiler/rustc_parse/src/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2138,6 +2138,11 @@ pub enum UnescapeError {
#[subdiagnostic]
suggestion: MoreThanOneCharSugg,
},
#[diag(parse_nul_in_c_str)]
NulInCStr {
#[primary_span]
span: Span,
},
}

#[derive(Subdiagnostic)]
Expand Down
3 changes: 3 additions & 0 deletions compiler/rustc_parse/src/lexer/unescape_error_reporting.rs
Original file line number Diff line number Diff line change
Expand Up @@ -265,6 +265,9 @@ pub(crate) fn emit_unescape_error(
EscapeError::LoneSlash => {
handler.emit_err(UnescapeError::LoneSlash(err_span));
}
EscapeError::NulInCStr => {
handler.emit_err(UnescapeError::NulInCStr { span: err_span });
}
EscapeError::UnskippedWhitespaceWarning => {
let (c, char_span) = last_char();
handler.emit_warning(UnescapeError::UnskippedWhitespace {
Expand Down
2 changes: 0 additions & 2 deletions compiler/rustc_session/messages.ftl
Original file line number Diff line number Diff line change
Expand Up @@ -72,8 +72,6 @@ session_not_circumvent_feature = `-Zunleash-the-miri-inside-of-you` may not be u
session_not_supported = not supported
session_nul_in_c_str = null characters in C string literals are not supported
session_octal_float_literal_not_supported = octal float literal is not supported
session_optimization_fuel_exhausted = optimization-fuel-exhausted: {$msg}
Expand Down
15 changes: 1 addition & 14 deletions compiler/rustc_session/src/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ use rustc_ast::token;
use rustc_ast::util::literal::LitError;
use rustc_errors::{error_code, DiagnosticMessage, ErrorGuaranteed, IntoDiagnostic, MultiSpan};
use rustc_macros::Diagnostic;
use rustc_span::{BytePos, Span, Symbol};
use rustc_span::{Span, Symbol};
use rustc_target::spec::{SplitDebuginfo, StackProtector, TargetTriple};

pub struct FeatureGateError {
Expand Down Expand Up @@ -329,13 +329,6 @@ pub(crate) struct BinaryFloatLiteralNotSupported {
pub span: Span,
}

#[derive(Diagnostic)]
#[diag(session_nul_in_c_str)]
pub(crate) struct NulInCStr {
#[primary_span]
pub span: Span,
}

pub fn report_lit_error(sess: &ParseSess, err: LitError, lit: token::Lit, span: Span) {
// Checks if `s` looks like i32 or u1234 etc.
fn looks_like_width_suffix(first_chars: &[char], s: &str) -> bool {
Expand Down Expand Up @@ -414,12 +407,6 @@ pub fn report_lit_error(sess: &ParseSess, err: LitError, lit: token::Lit, span:
};
sess.emit_err(IntLiteralTooLarge { span, limit });
}
LitError::NulInCStr(range) => {
let lo = BytePos(span.lo().0 + range.start as u32 + 2);
let hi = BytePos(span.lo().0 + range.end as u32 + 2);
let span = span.with_lo(lo).with_hi(hi);
sess.emit_err(NulInCStr { span });
}
}
}

Expand Down
Binary file modified tests/ui/rfcs/rfc-3348-c-string-literals/no-nuls.stderr
Binary file not shown.

0 comments on commit 25dd832

Please sign in to comment.