Skip to content

Commit ecd5ad6

Browse files
authored
Fix interpreter discovery panic on non-UTF-8 stdout (#526)
Fixes #525
1 parent 3cb303a commit ecd5ad6

1 file changed

Lines changed: 28 additions & 6 deletions

File tree

  • crates/pet-python-utils/src

‎crates/pet-python-utils/src/env.rs‎

Lines changed: 28 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -158,15 +158,20 @@ fn get_interpreter_details_with_timeout(
158158
let result = child.wait_with_output();
159159
match result {
160160
Ok(output) => {
161-
let output = String::from_utf8(output.stdout).unwrap().trim().to_string();
161+
let output = output.stdout;
162162
trace!(
163163
"Executed Python {:?} in {:?} & produced an output {:?}",
164164
executable,
165165
start.elapsed(),
166-
output
166+
String::from_utf8_lossy(&output)
167167
);
168-
if let Some((_, output)) = output.split_once(PYTHON_INFO_JSON_SEPARATOR) {
169-
if let Ok(info) = serde_json::from_str::<InterpreterInfo>(output) {
168+
let separator = PYTHON_INFO_JSON_SEPARATOR.as_bytes();
169+
if let Some(position) = output
170+
.windows(separator.len())
171+
.position(|bytes| bytes == separator)
172+
{
173+
let output = &output[position + separator.len()..];
174+
if let Ok(info) = serde_json::from_slice::<InterpreterInfo>(output) {
170175
let mut symlinks = vec![
171176
PathBuf::from(executable),
172177
PathBuf::from(info.executable.clone()),
@@ -183,14 +188,15 @@ fn get_interpreter_details_with_timeout(
183188
} else {
184189
error!(
185190
"Python Execution for {:?} produced an output {:?} that could not be parsed as JSON",
186-
executable, output,
191+
executable, String::from_utf8_lossy(output),
187192
);
188193
None
189194
}
190195
} else {
191196
error!(
192197
"Python Execution for {:?} produced an output {:?} without a separator",
193-
executable, output,
198+
executable,
199+
String::from_utf8_lossy(&output),
194200
);
195201
None
196202
}
@@ -210,6 +216,22 @@ mod tests {
210216
use super::*;
211217
use std::os::unix::fs::PermissionsExt;
212218

219+
// https://github.com/microsoft/python-environment-tools/issues/525:
220+
// A launcher printing GBK-encoded "文件不存在" must not panic discovery.
221+
#[test]
222+
fn get_interpreter_details_handles_non_utf8_stdout() -> std::io::Result<()> {
223+
let directory = tempfile::tempdir()?;
224+
let executable = directory.path().join("python");
225+
std::fs::write(
226+
&executable,
227+
"#!/bin/sh\nprintf '\\316\\304\\274\\376\\262\\273\\264\\346\\324\\332: -c\\r\\n'\n",
228+
)?;
229+
std::fs::set_permissions(&executable, std::fs::Permissions::from_mode(0o755))?;
230+
let result = get_interpreter_details_with_timeout(&executable, Duration::from_secs(5));
231+
assert!(result.is_none());
232+
directory.close()
233+
}
234+
213235
/// Regression test for #463: a spawn that never exits must not block the
214236
/// resolve path indefinitely. We use a shell script that sleeps far longer
215237
/// than the test timeout and assert that the call returns None promptly

0 commit comments

Comments
 (0)