(source: &[u8], filename: &str, vm: &VirtualMachine)
| 190 | |
| 191 | #[cfg(feature = "parser")] |
| 192 | fn decode_source_bytes(source: &[u8], filename: &str, vm: &VirtualMachine) -> PyResult<String> { |
| 193 | let has_bom = source.starts_with(b"\xef\xbb\xbf"); |
| 194 | let encoding = detect_source_encoding(source); |
| 195 | |
| 196 | let is_utf8 = encoding.as_deref().is_none_or(is_utf8_encoding); |
| 197 | |
| 198 | // Validate BOM + encoding combination |
| 199 | if has_bom && !is_utf8 { |
| 200 | return Err(vm.new_exception_msg( |
| 201 | vm.ctx.exceptions.syntax_error.to_owned(), |
| 202 | format!("encoding problem for '{filename}': utf-8").into(), |
| 203 | )); |
| 204 | } |
| 205 | |
| 206 | if is_utf8 { |
| 207 | let src = if has_bom { &source[3..] } else { source }; |
| 208 | match core::str::from_utf8(src) { |
| 209 | Ok(s) => Ok(s.to_owned()), |
| 210 | Err(e) => { |
| 211 | let bad_byte = src[e.valid_up_to()]; |
| 212 | let line = src[..e.valid_up_to()] |
| 213 | .iter() |
| 214 | .filter(|&&b| b == b'\n') |
| 215 | .count() |
| 216 | + 1; |
| 217 | Err(vm.new_exception_msg( |
| 218 | vm.ctx.exceptions.syntax_error.to_owned(), |
| 219 | format!( |
| 220 | "Non-UTF-8 code starting with '\\x{bad_byte:02x}' \ |
| 221 | on line {line}, but no encoding declared; \ |
| 222 | see https://peps.python.org/pep-0263/ for details \ |
| 223 | ({filename}, line {line})" |
| 224 | ) |
| 225 | .into(), |
| 226 | )) |
| 227 | } |
| 228 | } |
| 229 | } else { |
| 230 | // Use codec registry for non-UTF-8 encodings |
| 231 | let enc = encoding.as_deref().unwrap(); |
| 232 | let bytes_obj = vm.ctx.new_bytes(source.to_vec()); |
| 233 | let decoded = vm |
| 234 | .state |
| 235 | .codec_registry |
| 236 | .decode_text(bytes_obj.into(), enc, None, vm) |
| 237 | .map_err(|exc| { |
| 238 | if exc.fast_isinstance(vm.ctx.exceptions.lookup_error) { |
| 239 | vm.new_exception_msg( |
| 240 | vm.ctx.exceptions.syntax_error.to_owned(), |
| 241 | format!("unknown encoding for '{filename}': {enc}").into(), |
| 242 | ) |
| 243 | } else { |
| 244 | exc |
| 245 | } |
| 246 | })?; |
| 247 | Ok(decoded.to_string_lossy().into_owned()) |
| 248 | } |
| 249 | } |
no test coverage detected