Merge pull request #2299 from PyO3/hex-intp-cfg

Use more robust hexadecimal escaping of interpreter configuration.
This commit is contained in:
Adam Reichold 2022-04-12 22:25:40 +02:00 committed by GitHub
commit 9e605da761
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 63 additions and 16 deletions

View File

@ -21,6 +21,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Fixed ### Fixed
- Fix segfault when calling FFI methods `PyDateTime_DATE_GET_TZINFO` or `PyDateTime_TIME_GET_TZINFO` on `datetime` or `time` without a tzinfo. [#2289](https://github.com/PyO3/pyo3/pull/2289) - Fix segfault when calling FFI methods `PyDateTime_DATE_GET_TZINFO` or `PyDateTime_TIME_GET_TZINFO` on `datetime` or `time` without a tzinfo. [#2289](https://github.com/PyO3/pyo3/pull/2289)
- Fix directory names starting with the letter `n` breaking serialization of the interpreter configuration on Windows since PyO3 0.16.3. [#2299](https://github.com/PyO3/pyo3/pull/2299)
## [0.16.3] - 2022-04-05 ## [0.16.3] - 2022-04-05

View File

@ -366,7 +366,7 @@ print("mingw", get_platform().startswith("mingw"))
#[doc(hidden)] #[doc(hidden)]
pub fn from_cargo_dep_env() -> Option<Result<Self>> { pub fn from_cargo_dep_env() -> Option<Result<Self>> {
cargo_env_var("DEP_PYTHON_PYO3_CONFIG") cargo_env_var("DEP_PYTHON_PYO3_CONFIG")
.map(|buf| InterpreterConfig::from_reader(buf.replace("\\n", "\n").as_bytes())) .map(|buf| InterpreterConfig::from_reader(&*unescape(&buf)))
} }
#[doc(hidden)] #[doc(hidden)]
@ -464,11 +464,7 @@ print("mingw", get_platform().startswith("mingw"))
let mut buf = Vec::new(); let mut buf = Vec::new();
self.to_writer(&mut buf)?; self.to_writer(&mut buf)?;
// escape newlines in env var // escape newlines in env var
if let Ok(config) = str::from_utf8(&buf) { println!("cargo:PYO3_CONFIG={}", escape(&buf));
println!("cargo:PYO3_CONFIG={}", config.replace('\n', "\\n"));
} else {
bail!("unable to emit interpreter config to link env for downstream use");
}
Ok(()) Ok(())
} }
@ -1688,9 +1684,42 @@ pub fn make_interpreter_config() -> Result<InterpreterConfig> {
} }
} }
fn escape(bytes: &[u8]) -> String {
let mut escaped = String::with_capacity(2 * bytes.len());
for byte in bytes {
const LUT: &[u8; 16] = b"0123456789abcdef";
escaped.push(LUT[(byte >> 4) as usize] as char);
escaped.push(LUT[(byte & 0x0F) as usize] as char);
}
escaped
}
fn unescape(escaped: &str) -> Vec<u8> {
assert!(escaped.len() % 2 == 0, "invalid hex encoding");
let mut bytes = Vec::with_capacity(escaped.len() / 2);
for chunk in escaped.as_bytes().chunks_exact(2) {
fn unhex(hex: u8) -> u8 {
match hex {
b'a'..=b'f' => hex - b'a' + 10,
b'0'..=b'9' => hex - b'0',
_ => panic!("invalid hex encoding"),
}
}
bytes.push(unhex(chunk[0]) << 4 | unhex(chunk[1]));
}
bytes
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use std::{io::Cursor, iter::FromIterator}; use std::iter::FromIterator;
use target_lexicon::triple; use target_lexicon::triple;
use super::*; use super::*;
@ -1713,10 +1742,7 @@ mod tests {
let mut buf: Vec<u8> = Vec::new(); let mut buf: Vec<u8> = Vec::new();
config.to_writer(&mut buf).unwrap(); config.to_writer(&mut buf).unwrap();
assert_eq!( assert_eq!(config, InterpreterConfig::from_reader(&*buf).unwrap());
config,
InterpreterConfig::from_reader(Cursor::new(buf)).unwrap()
);
// And some different options, for variety // And some different options, for variety
@ -1744,17 +1770,37 @@ mod tests {
let mut buf: Vec<u8> = Vec::new(); let mut buf: Vec<u8> = Vec::new();
config.to_writer(&mut buf).unwrap(); config.to_writer(&mut buf).unwrap();
assert_eq!( assert_eq!(config, InterpreterConfig::from_reader(&*buf).unwrap());
config, }
InterpreterConfig::from_reader(Cursor::new(buf)).unwrap()
); #[test]
fn test_config_file_roundtrip_with_escaping() {
let config = InterpreterConfig {
abi3: true,
build_flags: BuildFlags::default(),
pointer_width: Some(32),
executable: Some("executable".into()),
implementation: PythonImplementation::CPython,
lib_name: Some("lib_name".into()),
lib_dir: Some("lib_dir\\n".into()),
shared: true,
version: MINIMUM_SUPPORTED_VERSION,
suppress_build_script_link_lines: true,
extra_build_script_lines: vec!["cargo:test1".to_string(), "cargo:test2".to_string()],
};
let mut buf: Vec<u8> = Vec::new();
config.to_writer(&mut buf).unwrap();
let buf = unescape(&escape(&buf));
assert_eq!(config, InterpreterConfig::from_reader(&*buf).unwrap());
} }
#[test] #[test]
fn test_config_file_defaults() { fn test_config_file_defaults() {
// Only version is required // Only version is required
assert_eq!( assert_eq!(
InterpreterConfig::from_reader(Cursor::new("version=3.7")).unwrap(), InterpreterConfig::from_reader("version=3.7".as_bytes()).unwrap(),
InterpreterConfig { InterpreterConfig {
version: PythonVersion { major: 3, minor: 7 }, version: PythonVersion { major: 3, minor: 7 },
implementation: PythonImplementation::CPython, implementation: PythonImplementation::CPython,