mirror of
https://github.com/RustPython/RustPython.git
synced 2026-10-28 18:59:04 +08:00
Fix -m argv[0] and file SyntaxError display (#8684)
* Fix -m argv[0] and file SyntaxError display Set sys.argv[0] to -m for module execution. Convert an escalated invalid-escape SyntaxWarning into a SyntaxError that spans just the `\X` pair. Report null bytes in source files with filename, lineno, and the truncated line. Enable the four corresponding test_cmd_line_script cases. Assisted-by: Grok:4.6 * Pass SyntaxError text when escalating escape warnings Keep the warning and SyntaxError messages as separate strings at the invalid-escape site. CompileWarningError carries an optional replacement instead of rewriting the warning text later. Assisted-by: Grok:4.6 * Remove expectedFailure from bytes invalid-escape test The invalid-escape SyntaxError path now satisfies test_eval_bytes_invalid_escape. Assisted-by: Grok:4.6
This commit is contained in:
1 parent
1345a82443
commit
cfee8a7ffa
5 files changed
+76
-24
No files matched your search
Vendored
-4
@@ -387,7 +387,6 @@ class CmdLineTest(unittest.TestCase):
|
||||
"be directly executed")
|
||||
self._check_import_error(["-m", "test_pkg"], msg, cwd=script_dir)
|
||||
|
||||
@unittest.expectedFailure # TODO: RUSTPYTHON
|
||||
def test_issue8202(self):
|
||||
# Make sure package __init__ modules see "-m" in sys.argv0 while
|
||||
# searching for the module to execute
|
||||
@@ -656,7 +655,6 @@ class CmdLineTest(unittest.TestCase):
|
||||
],
|
||||
)
|
||||
|
||||
@unittest.expectedFailure # TODO: RUSTPYTHON
|
||||
def test_syntaxerror_invalid_escape_sequence_multi_line(self):
|
||||
script = 'foo = """\\q"""\n'
|
||||
with os_helper.temp_dir() as script_dir:
|
||||
@@ -673,7 +671,6 @@ class CmdLineTest(unittest.TestCase):
|
||||
],
|
||||
)
|
||||
|
||||
@unittest.expectedFailure # TODO: RUSTPYTHON
|
||||
def test_syntaxerror_null_bytes(self):
|
||||
script = "x = '\0' nothing to see here\n';import os;os.system('echo pwnd')\n"
|
||||
with os_helper.temp_dir() as script_dir:
|
||||
@@ -686,7 +683,6 @@ class CmdLineTest(unittest.TestCase):
|
||||
],
|
||||
)
|
||||
|
||||
@unittest.expectedFailure # TODO: RUSTPYTHON
|
||||
def test_syntaxerror_null_bytes_in_multiline_string(self):
|
||||
scripts = ["\n'''\nmultilinestring\0\n'''", "\nf'''\nmultilinestring\0\n'''"] # Both normal and f-strings
|
||||
with os_helper.temp_dir() as script_dir:
|
||||
|
||||
Vendored
-1
@@ -225,7 +225,6 @@ class TestLiterals(unittest.TestCase):
|
||||
self.assertRaises(SyntaxError, eval, r""" b'\x' """)
|
||||
self.assertRaises(SyntaxError, eval, r""" b'\x0' """)
|
||||
|
||||
@unittest.expectedFailure # TODO: RUSTPYTHON
|
||||
def test_eval_bytes_invalid_escape(self):
|
||||
for b in range(1, 128):
|
||||
if b in b"""\n\r"'01234567\\abfnrtvx""":
|
||||
|
||||
+46
-11
@@ -26,6 +26,13 @@ pub struct CompileWarningError {
|
||||
filename: String,
|
||||
lineno: usize,
|
||||
offset: usize,
|
||||
replacement: Option<SyntaxErrorReplacement>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct SyntaxErrorReplacement {
|
||||
message: String,
|
||||
end_offset: usize,
|
||||
}
|
||||
|
||||
impl From<CompileError> for VmCompileError {
|
||||
@@ -71,13 +78,16 @@ impl CompileWarningError {
|
||||
{
|
||||
return self.exception;
|
||||
}
|
||||
let Ok(message) = self.exception.as_object().str(vm) else {
|
||||
return self.exception;
|
||||
let (message, end_offset) = if let Some(replacement) = self.replacement {
|
||||
(replacement.message, Some(replacement.end_offset))
|
||||
} else {
|
||||
let Ok(message) = self.exception.as_object().str(vm) else {
|
||||
return self.exception;
|
||||
};
|
||||
(message.to_string_lossy().into_owned(), None)
|
||||
};
|
||||
let syntax_error = vm.new_exception_msg(
|
||||
vm.ctx.exceptions.syntax_error.to_owned(),
|
||||
message.as_wtf8().to_owned(),
|
||||
);
|
||||
let syntax_error =
|
||||
vm.new_exception_msg(vm.ctx.exceptions.syntax_error.to_owned(), message.into());
|
||||
syntax_error
|
||||
.as_object()
|
||||
.set_attr("lineno", vm.ctx.new_int(self.lineno), vm)
|
||||
@@ -86,6 +96,16 @@ impl CompileWarningError {
|
||||
.as_object()
|
||||
.set_attr("offset", vm.ctx.new_int(self.offset), vm)
|
||||
.unwrap();
|
||||
if let Some(end_offset) = end_offset {
|
||||
syntax_error
|
||||
.as_object()
|
||||
.set_attr("end_lineno", vm.ctx.new_int(self.lineno), vm)
|
||||
.unwrap();
|
||||
syntax_error
|
||||
.as_object()
|
||||
.set_attr("end_offset", vm.ctx.new_int(end_offset), vm)
|
||||
.unwrap();
|
||||
}
|
||||
syntax_error
|
||||
.as_object()
|
||||
.set_attr("filename", vm.ctx.new_str(self.filename), vm)
|
||||
@@ -484,6 +504,7 @@ mod escape_warnings {
|
||||
filename: filename.to_owned(),
|
||||
lineno,
|
||||
offset,
|
||||
replacement: None,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -609,16 +630,20 @@ mod escape_warnings {
|
||||
filename: &str,
|
||||
vm: &VirtualMachine,
|
||||
) -> Result<(), CompileWarningError> {
|
||||
let lineno = line_number_at(source, offset);
|
||||
let message = vm.ctx.new_str(format!(
|
||||
let (lineno, column) = line_offset_at(source, offset);
|
||||
let warning = format!(
|
||||
"\"\\{ch}\" is an invalid escape sequence. \
|
||||
Such sequences will not work in the future. \
|
||||
Did you mean \"\\\\{ch}\"? A raw string is also an option."
|
||||
));
|
||||
);
|
||||
let syntax_error = format!(
|
||||
"\"\\{ch}\" is an invalid escape sequence. \
|
||||
Did you mean \"\\\\{ch}\"? A raw string is also an option."
|
||||
);
|
||||
let fname = vm.ctx.new_str(filename);
|
||||
warn::warn_explicit(
|
||||
Some(vm.ctx.exceptions.syntax_warning.to_owned()),
|
||||
message.into(),
|
||||
vm.ctx.new_str(warning).into(),
|
||||
fname,
|
||||
lineno,
|
||||
None,
|
||||
@@ -627,7 +652,16 @@ mod escape_warnings {
|
||||
None,
|
||||
vm,
|
||||
)
|
||||
.map_err(|err| compile_warning_error(err, source, filename, offset))
|
||||
.map_err(|exception| CompileWarningError {
|
||||
exception,
|
||||
filename: filename.to_owned(),
|
||||
lineno,
|
||||
offset: column,
|
||||
replacement: Some(SyntaxErrorReplacement {
|
||||
message: syntax_error,
|
||||
end_offset: column + 2,
|
||||
}),
|
||||
})
|
||||
}
|
||||
|
||||
fn warn_syntax_at_offset(
|
||||
@@ -678,6 +712,7 @@ mod escape_warnings {
|
||||
filename: filename.to_owned(),
|
||||
lineno: location.line.get(),
|
||||
offset: location.character_offset.get(),
|
||||
replacement: None,
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -55,8 +55,8 @@ impl VirtualMachine {
|
||||
#[cfg(feature = "host_env")]
|
||||
mod file_run {
|
||||
use crate::{
|
||||
Py, PyResult, VirtualMachine,
|
||||
builtins::{PyCode, PyDict},
|
||||
AsObject, Py, PyResult, VirtualMachine,
|
||||
builtins::{PyBaseExceptionRef, PyCode, PyDict},
|
||||
compiler::{self},
|
||||
scope::Scope,
|
||||
};
|
||||
@@ -107,11 +107,8 @@ mod file_run {
|
||||
}
|
||||
match crate::host_env::fs::read(path) {
|
||||
Ok(source_bytes) => {
|
||||
if source_bytes.contains(&0) {
|
||||
return Err(self.new_exception_msg(
|
||||
self.ctx.exceptions.syntax_error.to_owned(),
|
||||
"source code cannot contain null bytes".into(),
|
||||
));
|
||||
if let Some(null_at) = source_bytes.iter().position(|&b| b == 0) {
|
||||
return Err(null_byte_syntax_error(self, path, &source_bytes, null_at));
|
||||
}
|
||||
#[cfg(feature = "parser")]
|
||||
// Match compile() by honoring BOMs and encoding cookies in files.
|
||||
@@ -138,6 +135,31 @@ mod file_run {
|
||||
}
|
||||
}
|
||||
|
||||
fn null_byte_syntax_error(
|
||||
vm: &VirtualMachine,
|
||||
path: &str,
|
||||
source_bytes: &[u8],
|
||||
null_at: usize,
|
||||
) -> PyBaseExceptionRef {
|
||||
let before = &source_bytes[..null_at];
|
||||
let lineno = before.iter().filter(|&&b| b == b'\n').count() + 1;
|
||||
let line_start = before
|
||||
.iter()
|
||||
.rposition(|&b| b == b'\n')
|
||||
.map_or(0, |i| i + 1);
|
||||
let line = String::from_utf8_lossy(&source_bytes[line_start..null_at]);
|
||||
let syntax_error = vm.new_exception_msg(
|
||||
vm.ctx.exceptions.syntax_error.to_owned(),
|
||||
"source code cannot contain null bytes".into(),
|
||||
);
|
||||
let obj = syntax_error.as_object();
|
||||
obj.set_attr("filename", vm.ctx.new_str(path), vm).unwrap();
|
||||
obj.set_attr("lineno", vm.ctx.new_int(lineno), vm).unwrap();
|
||||
obj.set_attr("text", vm.ctx.new_str(format!("{line}\n")), vm)
|
||||
.unwrap();
|
||||
syntax_error
|
||||
}
|
||||
|
||||
fn set_main_loader(
|
||||
module_dict: &Py<PyDict>,
|
||||
filename: &str,
|
||||
|
||||
+1
-1
@@ -136,7 +136,7 @@ fn parse_args() -> Result<(CliArgs, RunMode, Vec<String>), lexopt::Error> {
|
||||
Short('I') => args.isolate = true,
|
||||
Short('m') => {
|
||||
let module = parser.value()?.string()?;
|
||||
let argv = argv("PLACEHOLDER".to_owned(), parser)?;
|
||||
let argv = argv("-m".to_owned(), parser)?;
|
||||
return Ok((args, RunMode::Module(module), argv));
|
||||
}
|
||||
Short('O') => args.optimize += 1,
|
||||
|
||||
Reference in new issue
Block a user