Skip to content

Commit

Permalink
Merge pull request #2299 from PyO3/hex-intp-cfg
Browse files Browse the repository at this point in the history
Use more robust hexadecimal escaping of interpreter configuration.
  • Loading branch information
adamreichold committed Apr 12, 2022
2 parents 366bcd1 + 58b7081 commit 9e605da
Show file tree
Hide file tree
Showing 2 changed files with 63 additions and 16 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Expand Up @@ -21,6 +21,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### 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 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

Expand Down
78 changes: 62 additions & 16 deletions pyo3-build-config/src/impl_.rs
Expand Up @@ -366,7 +366,7 @@ print("mingw", get_platform().startswith("mingw"))
#[doc(hidden)]
pub fn from_cargo_dep_env() -> Option<Result<Self>> {
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)]
Expand Down Expand Up @@ -464,11 +464,7 @@ print("mingw", get_platform().startswith("mingw"))
let mut buf = Vec::new();
self.to_writer(&mut buf)?;
// escape newlines in env var
if let Ok(config) = str::from_utf8(&buf) {
println!("cargo:PYO3_CONFIG={}", config.replace('\n', "\\n"));
} else {
bail!("unable to emit interpreter config to link env for downstream use");
}
println!("cargo:PYO3_CONFIG={}", escape(&buf));
Ok(())
}

Expand Down Expand Up @@ -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)]
mod tests {
use std::{io::Cursor, iter::FromIterator};
use std::iter::FromIterator;
use target_lexicon::triple;

use super::*;
Expand All @@ -1713,10 +1742,7 @@ mod tests {
let mut buf: Vec<u8> = Vec::new();
config.to_writer(&mut buf).unwrap();

assert_eq!(
config,
InterpreterConfig::from_reader(Cursor::new(buf)).unwrap()
);
assert_eq!(config, InterpreterConfig::from_reader(&*buf).unwrap());

// And some different options, for variety

Expand Down Expand Up @@ -1744,17 +1770,37 @@ mod tests {
let mut buf: Vec<u8> = Vec::new();
config.to_writer(&mut buf).unwrap();

assert_eq!(
config,
InterpreterConfig::from_reader(Cursor::new(buf)).unwrap()
);
assert_eq!(config, InterpreterConfig::from_reader(&*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]
fn test_config_file_defaults() {
// Only version is required
assert_eq!(
InterpreterConfig::from_reader(Cursor::new("version=3.7")).unwrap(),
InterpreterConfig::from_reader("version=3.7".as_bytes()).unwrap(),
InterpreterConfig {
version: PythonVersion { major: 3, minor: 7 },
implementation: PythonImplementation::CPython,
Expand Down

0 comments on commit 9e605da

Please sign in to comment.