Skip to content

Commit 069a157

Browse files
committed
rust: add validation of extension modules
I'm going to be making some changes to the extension module configuration mechanism in order to support Python 3.11. In preparation for this, this commit establishes some static validation rules for extension module presence testing. This will hopefully be sufficient to find changes in behavior when we make code changes.
1 parent a6cf446 commit 069a157

File tree

1 file changed

+247
-0
lines changed

1 file changed

+247
-0
lines changed

src/validation.rs

Lines changed: 247 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -535,6 +535,139 @@ const GLOBALLY_BANNED_EXTENSIONS: &[&str] = &[
535535
"nis",
536536
];
537537

538+
const GLOBAL_EXTENSIONS: &[&str] = &[
539+
"_abc",
540+
"_ast",
541+
"_asyncio",
542+
"_bisect",
543+
"_blake2",
544+
"_bz2",
545+
"_codecs",
546+
"_codecs_cn",
547+
"_codecs_hk",
548+
"_codecs_iso2022",
549+
"_codecs_jp",
550+
"_codecs_kr",
551+
"_codecs_tw",
552+
"_collections",
553+
"_contextvars",
554+
"_csv",
555+
"_ctypes",
556+
"_datetime",
557+
"_decimal",
558+
"_elementtree",
559+
"_functools",
560+
"_hashlib",
561+
"_heapq",
562+
"_imp",
563+
"_io",
564+
"_json",
565+
"_locale",
566+
"_lsprof",
567+
"_lzma",
568+
"_md5",
569+
"_multibytecodec",
570+
"_multiprocessing",
571+
"_opcode",
572+
"_operator",
573+
"_pickle",
574+
"_queue",
575+
"_random",
576+
"_sha1",
577+
"_sha256",
578+
"_sha3",
579+
"_sha512",
580+
"_signal",
581+
"_socket",
582+
"_sqlite3",
583+
"_sre",
584+
"_ssl",
585+
"_stat",
586+
"_statistics",
587+
"_string",
588+
"_struct",
589+
"_symtable",
590+
"_thread",
591+
"_tkinter",
592+
"_tracemalloc",
593+
"_warnings",
594+
"_weakref",
595+
"array",
596+
"atexit",
597+
"audioop",
598+
"binascii",
599+
"builtins",
600+
"cmath",
601+
"errno",
602+
"faulthandler",
603+
"gc",
604+
"itertools",
605+
"marshal",
606+
"math",
607+
"mmap",
608+
"pyexpat",
609+
"select",
610+
"sys",
611+
"time",
612+
"unicodedata",
613+
"xxsubtype",
614+
"zlib",
615+
];
616+
617+
// _zoneinfo added in 3.9.
618+
// parser removed in 3.10.
619+
620+
// We didn't build ctypes_test until 3.9.
621+
// We didn't build some test extensions until 3.9.
622+
623+
const GLOBAL_EXTENSIONS_PYTHON_3_8: &[&str] = &["parser"];
624+
625+
const GLOBAL_EXTENSIONS_PYTHON_3_9: &[&str] = &[
626+
"_peg_parser",
627+
"_uuid",
628+
"_xxsubinterpreters",
629+
"_zoneinfo",
630+
"parser",
631+
];
632+
633+
const GLOBAL_EXTENSIONS_PYTHON_3_10: &[&str] = &["_uuid", "_xxsubinterpreters", "_zoneinfo"];
634+
635+
const GLOBAL_EXTENSIONS_MACOS: &[&str] = &["_scproxy"];
636+
637+
const GLOBAL_EXTENSIONS_POSIX: &[&str] = &[
638+
"_crypt",
639+
"_curses",
640+
"_curses_panel",
641+
"_dbm",
642+
"_posixshmem",
643+
"_posixsubprocess",
644+
"_testinternalcapi",
645+
"fcntl",
646+
"grp",
647+
"posix",
648+
"pwd",
649+
"readline",
650+
"resource",
651+
"syslog",
652+
"termios",
653+
];
654+
655+
const GLOBAL_EXTENSIONS_LINUX: &[&str] = &["spwd"];
656+
657+
const GLOBAL_EXTENSIONS_WINDOWS: &[&str] = &[
658+
"_msi",
659+
"_overlapped",
660+
"_winapi",
661+
"_xxsubinterpreters",
662+
"msvcrt",
663+
"nt",
664+
"winreg",
665+
"winsound",
666+
];
667+
668+
/// Extension modules not present in Windows static builds.
669+
const GLOBAL_EXTENSIONS_WINDOWS_NO_STATIC: &[&str] = &["_testinternalcapi", "_tkinter"];
670+
538671
const PYTHON_VERIFICATIONS: &str = include_str!("verify_distribution.py");
539672

540673
fn allowed_dylibs_for_triple(triple: &str) -> Vec<MachOAllowedDylib> {
@@ -1109,6 +1242,103 @@ fn validate_possible_object_file(
11091242
Ok(context)
11101243
}
11111244

1245+
fn validate_extension_modules(
1246+
python_major_minor: &str,
1247+
target_triple: &str,
1248+
static_crt: bool,
1249+
have_extensions: &BTreeSet<&str>,
1250+
) -> Result<Vec<String>> {
1251+
let mut errors = vec![];
1252+
1253+
let is_macos = target_triple.contains("-apple-darwin");
1254+
let is_linux = target_triple.contains("-unknown-linux-");
1255+
let is_windows = target_triple.contains("-pc-windows-");
1256+
let is_linux_musl = target_triple.contains("-unknown-linux-musl");
1257+
1258+
let mut wanted = BTreeSet::from_iter(GLOBAL_EXTENSIONS.iter().map(|x| *x));
1259+
1260+
match python_major_minor {
1261+
"3.8" => {
1262+
wanted.extend(GLOBAL_EXTENSIONS_PYTHON_3_8);
1263+
}
1264+
"3.9" => {
1265+
wanted.extend(GLOBAL_EXTENSIONS_PYTHON_3_9);
1266+
}
1267+
"3.10" => {
1268+
wanted.extend(GLOBAL_EXTENSIONS_PYTHON_3_10);
1269+
}
1270+
_ => {
1271+
panic!("unhandled Python version: {}", python_major_minor);
1272+
}
1273+
}
1274+
1275+
if is_macos {
1276+
wanted.extend(GLOBAL_EXTENSIONS_POSIX);
1277+
wanted.extend(GLOBAL_EXTENSIONS_MACOS);
1278+
}
1279+
1280+
if is_windows {
1281+
wanted.extend(GLOBAL_EXTENSIONS_WINDOWS);
1282+
1283+
if static_crt {
1284+
for x in GLOBAL_EXTENSIONS_WINDOWS_NO_STATIC {
1285+
wanted.remove(*x);
1286+
}
1287+
}
1288+
}
1289+
1290+
if is_linux {
1291+
wanted.extend(GLOBAL_EXTENSIONS_POSIX);
1292+
wanted.extend(GLOBAL_EXTENSIONS_LINUX);
1293+
1294+
if !is_linux_musl {
1295+
wanted.insert("ossaudiodev");
1296+
}
1297+
}
1298+
1299+
if (is_linux || is_macos) && matches!(python_major_minor, "3.9" | "3.10") {
1300+
wanted.extend([
1301+
"_ctypes_test",
1302+
"_testbuffer",
1303+
"_testimportmultiple",
1304+
"_testmultiphase",
1305+
"_xxtestfuzz",
1306+
]);
1307+
}
1308+
1309+
// _gdbm Linux only until 3.10, where it was dropped by us.
1310+
if is_linux && matches!(python_major_minor, "3.8" | "3.9") {
1311+
// But it isn't present on i686 due to build issues.
1312+
if target_triple != "i686-unknown-linux-gnu" {
1313+
wanted.insert("_gdbm");
1314+
}
1315+
}
1316+
1317+
// _uuid is POSIX only on 3.8. On 3.9, it is global.
1318+
if python_major_minor == "3.8" {
1319+
if is_linux || is_macos {
1320+
wanted.insert("_uuid");
1321+
}
1322+
} else {
1323+
wanted.insert("_uuid");
1324+
}
1325+
1326+
// _ctypes_test was only added to some cross builds on 3.8.
1327+
if python_major_minor == "3.8" && target_triple == "aarch64-unknown-linux-gnu" {
1328+
wanted.insert("_ctypes_test");
1329+
}
1330+
1331+
for extra in have_extensions.difference(&wanted) {
1332+
errors.push(format!("extra/unknown extension module: {}", extra));
1333+
}
1334+
1335+
for missing in wanted.difference(have_extensions) {
1336+
errors.push(format!("missing extension module: {}", missing));
1337+
}
1338+
1339+
Ok(errors)
1340+
}
1341+
11121342
fn validate_json(json: &PythonJsonMain, triple: &str, is_debug: bool) -> Result<Vec<String>> {
11131343
let mut errors = vec![];
11141344

@@ -1163,6 +1393,23 @@ fn validate_json(json: &PythonJsonMain, triple: &str, is_debug: bool) -> Result<
11631393
}
11641394
}
11651395

1396+
let have_extensions = json
1397+
.build_info
1398+
.extensions
1399+
.keys()
1400+
.map(|x| x.as_str())
1401+
.collect::<BTreeSet<_>>();
1402+
1403+
errors.extend(
1404+
validate_extension_modules(
1405+
&json.python_major_minor_version,
1406+
triple,
1407+
json.crt_features.contains(&"static".to_string()),
1408+
&have_extensions,
1409+
)?
1410+
.into_iter(),
1411+
);
1412+
11661413
Ok(errors)
11671414
}
11681415

0 commit comments

Comments
 (0)