use core::panic;
use pyc_editor::{
cfg::{BlockIndexInfo, BranchEdge, CFGInstructionIndex, ControlFlowGraph, InstructionIndexMap},
dump_pyc, load_pyc,
prelude::*,
traits::GenericInstruction,
utils::ExceptionTableEntry,
v310, v311, v312, v313,
};
use python_marshal::CodeFlags;
use python_marshal::magic::PyVersion;
use rayon::prelude::*;
use std::{
io::{BufReader, Write},
path::{Path, PathBuf},
};
use crate::common::DATA_PATH;
mod common;
macro_rules! handle_code_object_versions {
($code:expr, $handler:ident) => {
match $code {
pyc_editor::CodeObject::V310(code) => {
$handler!(V310, v310, code)
}
pyc_editor::CodeObject::V311(code) => {
$handler!(V311, v311, code)
}
pyc_editor::CodeObject::V312(code) => {
$handler!(V312, v312, code)
}
pyc_editor::CodeObject::V313(code) => {
$handler!(V313, v313, code)
}
}
};
}
macro_rules! handle_pyc_versions {
($pyc:expr, $handler:ident) => {
match $pyc {
pyc_editor::PycFile::V310(ref mut pyc) => {
$handler!(V310, v310, pyc)
}
pyc_editor::PycFile::V311(ref mut pyc) => {
$handler!(V311, v311, pyc)
}
pyc_editor::PycFile::V312(ref mut pyc) => {
$handler!(V312, v312, pyc)
}
pyc_editor::PycFile::V313(ref mut pyc) => {
$handler!(V313, v313, pyc)
}
}
};
($pyc:expr, $handler:ident, immutable) => {
match $pyc {
pyc_editor::PycFile::V310(ref pyc) => {
$handler!(V310, v310, pyc)
}
pyc_editor::PycFile::V311(ref pyc) => {
$handler!(V311, v311, pyc)
}
pyc_editor::PycFile::V312(ref pyc) => {
$handler!(V312, v312, pyc)
}
pyc_editor::PycFile::V313(ref pyc) => {
$handler!(V313, v313, pyc)
}
}
};
}
macro_rules! get_generator_stacksize {
(V313) => {
0
};
($variant:ident) => {
1
};
}
static LOGGER_INIT: std::sync::Once = std::sync::Once::new();
#[test]
fn test_recompile_standard_lib() {
LOGGER_INIT.call_once(|| {
common::setup();
env_logger::init();
});
common::PYTHON_VERSIONS.par_iter().for_each(|version| {
println!("Testing with Python version: {}", version);
let pyc_files = common::find_pyc_files(version);
pyc_files.par_iter().for_each(|pyc_file| {
println!("Testing pyc file: {:?}", pyc_file);
let file = std::fs::File::open(pyc_file).expect("Failed to open pyc file");
let reader = BufReader::new(file);
let original_pyc = python_marshal::load_pyc(reader).expect("Failed to load pyc file");
let original_pyc = python_marshal::resolver::resolve_all_refs(
&original_pyc.object,
&original_pyc.references,
)
.0;
let file = std::fs::File::open(pyc_file).expect("Failed to open pyc file");
let reader = BufReader::new(file);
let parsed_pyc = load_pyc(reader).unwrap();
let pyc: python_marshal::PycFile = parsed_pyc.clone().into();
std::assert_eq!(
original_pyc,
pyc.object,
"{:?} has not been recompiled succesfully",
&pyc_file
);
});
});
}
fn compare_instructions<T: SimpleInstructionAccess<I>, I: GenericInstruction>(
original_list: T,
new_list: T,
) -> bool {
let mut og_iter = original_list.as_ref().iter().enumerate();
let mut new_iter = new_list.as_ref().iter().enumerate();
let mut bug_indexes: Vec<usize> = vec![];
let mut possible_mismatches: Vec<(usize, usize)> = vec![];
while let Some((og_index, og_instruction)) = og_iter.next() {
if let Some((new_index, new_instruction)) = new_iter.next() {
if new_instruction != og_instruction
&& new_instruction.is_jump_backwards()
&& og_instruction.is_extended_arg()
{
let mut curr_instruction = og_instruction;
while curr_instruction.is_extended_arg() {
let prev_instruction = curr_instruction;
curr_instruction = match og_iter.next() {
None => return false,
Some((_, new_inst)) => new_inst,
};
if curr_instruction.is_extended_arg()
&& prev_instruction.get_raw_value().to_u8() != u8::MAX
{
return false;
}
}
if !(curr_instruction.is_jump_backwards()
&& curr_instruction.get_raw_value().to_u8() == 0)
{
possible_mismatches.push((og_index, new_index));
} else {
bug_indexes.push(new_index);
}
} else if new_instruction != og_instruction
&& new_instruction.get_opcode() == og_instruction.get_opcode()
&& new_instruction.is_jump()
{
possible_mismatches.push((og_index, new_index));
} else if new_instruction != og_instruction {
return false;
}
} else {
return false;
}
}
for (og_index, new_index) in possible_mismatches {
let bug_count = if new_list.as_ref().get(new_index).unwrap().is_absolute_jump() {
bug_indexes
.iter()
.filter(|bug_index| **bug_index < new_index)
.count()
} else if new_list.as_ref().get(new_index).unwrap().is_jump_forwards() {
bug_indexes
.iter()
.filter(|bug_index| {
new_index < **bug_index
&& **bug_index
< new_index + new_list.get_full_arg(new_index).unwrap() as usize + 1
})
.count()
} else if new_list
.as_ref()
.get(new_index)
.unwrap()
.is_jump_backwards()
{
bug_indexes
.iter()
.filter(|bug_index| {
new_index - new_list.get_full_arg(new_index).unwrap() as usize + 1 < **bug_index
&& **bug_index < new_index
})
.count()
} else {
unreachable!("It should always be a jump. We have covered all jump types.")
};
if new_list.get_full_arg(new_index).unwrap() + bug_count as u32
!= original_list.get_full_arg(og_index).unwrap()
{
return false;
}
}
true
}
#[test]
fn test_recompile_resolved_standard_lib() {
LOGGER_INIT.call_once(|| {
common::setup();
env_logger::init();
});
common::PYTHON_VERSIONS.par_iter().for_each(|version| {
println!("Testing with Python version: {}", version);
let pyc_files = common::find_pyc_files(version);
pyc_files.par_iter().for_each(|pyc_file| {
println!("Testing pyc file: {:?}", pyc_file);
let file = std::fs::File::open(pyc_file).expect("Failed to open pyc file");
let reader = BufReader::new(file);
let mut parsed_pyc = load_pyc(reader).unwrap();
fn rewrite_code_object(
code: pyc_editor::CodeObject,
) -> Result<pyc_editor::CodeObject, (pyc_editor::CodeObject, pyc_editor::CodeObject)>
{
macro_rules! rewrite_version {
($variant:ident, $module:ident, $code:expr) => {{
let mut code = $code.clone();
let mut new_code = $code.clone();
new_code.code =
new_code.code.to_resolved(None).unwrap().0.to_instructions();
match compare_instructions(
$code.code.iter().as_slice(),
new_code.code.iter().as_slice(),
) {
false => {
return Err((
pyc_editor::CodeObject::$variant(code),
pyc_editor::CodeObject::$variant(new_code),
));
}
true => {}
}
for constant in &mut code.consts {
if let &mut $module::code_objects::Constant::CodeObject(
ref mut const_code,
) = constant
{
rewrite_code_object(pyc_editor::CodeObject::$variant(
const_code.clone(),
))?;
}
}
Ok(pyc_editor::CodeObject::$variant(new_code))
}};
}
handle_code_object_versions!(code, rewrite_version)
}
macro_rules! rewrite_pyc {
($variant:ident, $module:ident, $pyc:expr) => {{
match rewrite_code_object(pyc_editor::CodeObject::$variant(
$pyc.code_object.clone(),
)) {
Ok(new_code) => new_code,
Err((
pyc_editor::CodeObject::$variant(code),
pyc_editor::CodeObject::$variant(new_code),
)) => {
println!("{:#?}", code);
println!("{:#?}", new_code);
panic!();
}
_ => unreachable!(),
};
}};
}
handle_pyc_versions!(parsed_pyc, rewrite_pyc);
});
});
}
#[test]
fn test_line_number_standard_lib() {
LOGGER_INIT.call_once(|| {
common::setup();
env_logger::init();
});
common::PYTHON_VERSIONS.par_iter().for_each(|version| {
println!("Testing with Python version: {}", version);
let pyc_files = common::find_pyc_files(version);
pyc_files.par_iter().for_each(|pyc_file| {
println!("Testing pyc file: {:?}", pyc_file);
let file = std::fs::File::open(pyc_file).expect("Failed to open pyc file");
let reader = BufReader::new(file);
let parsed_pyc = load_pyc(reader).unwrap();
fn recursive_code_object(code: &pyc_editor::CodeObject) {
macro_rules! recursive_version {
($variant:ident, $module:ident, $code:expr) => {{
let co_lines = $code.co_lines().unwrap();
for index in 0..$code.code.len() {
$module::instructions::get_line_number(&co_lines, index as u32);
}
for constant in &$code.consts {
if let &$module::code_objects::Constant::CodeObject(ref const_code) =
constant
{
recursive_code_object(&pyc_editor::CodeObject::$variant(
const_code.clone(),
));
}
}
}};
}
handle_code_object_versions!(code, recursive_version);
}
macro_rules! call_recursive {
($variant:ident, $module:ident, $pyc:expr) => {{
recursive_code_object(&pyc_editor::CodeObject::$variant(
$pyc.code_object.clone(),
));
}};
}
handle_pyc_versions!(parsed_pyc, call_recursive, immutable);
});
});
}
#[test]
fn test_stacksize_standard_lib() {
LOGGER_INIT.call_once(|| {
common::setup();
env_logger::init();
});
static EXCEPTIONS: &[&str] = &[
"tests/data/cpython-3.11/Lib/test/__pycache__/test_except_star.cpython-311.pyc",
"tests/data/cpython-3.11/Lib/test/__pycache__/test_sys_settrace.cpython-311.pyc",
"tests/data/cpython-3.11/Lib/__pycache__/opcode.cpython-311.pyc", "tests/data/cpython-3.12/Lib/test/__pycache__/test_except_star.cpython-312.pyc",
"tests/data/cpython-3.12/Lib/test/__pycache__/test_sys_settrace.cpython-312.pyc",
"tests/data/cpython-3.13/Lib/test/__pycache__/test_except_star.cpython-313.pyc",
"tests/data/cpython-3.13/Lib/test/__pycache__/test_sys_settrace.cpython-313.pyc",
"tests/data/cpython-3.13/Lib/test/test_ctypes/__pycache__/test_bytes.cpython-313.pyc", ];
common::PYTHON_VERSIONS.par_iter().for_each(|version| {
println!("Testing with Python version: {}", version);
let pyc_files = common::find_pyc_files(version);
pyc_files.par_iter().for_each(|pyc_file| {
if EXCEPTIONS.iter().all(|exc| !pyc_file.ends_with(exc)) {
println!("Testing pyc file: {:?}", pyc_file);
let file = std::fs::File::open(pyc_file).expect("Failed to open pyc file");
let reader = BufReader::new(file);
let parsed_pyc = load_pyc(reader).unwrap();
fn recursive_code_object(code: &pyc_editor::CodeObject) {
macro_rules! get_exception_table {
(V310, $code:expr) => {
None
};
($variant:ident, $code:expr) => {
Some($code.exception_table().expect("Exception table is valid"))
};
}
macro_rules! allow_zero {
(V313, $code:expr) => {
false
};
($variant:ident, $code:expr) => {
true
};
}
macro_rules! recursive_version {
($variant:ident, $module:ident, $code:expr) => {{
let is_generator = $code.flags.intersects(
CodeFlags::GENERATOR
| CodeFlags::COROUTINE
| CodeFlags::ASYNC_GENERATOR
| CodeFlags::ITERABLE_COROUTINE,
);
let exception_table: Option<Vec<ExceptionTableEntry>> =
get_exception_table!($variant, $code);
assert_eq!(
$code
.code
.max_stack_size(
if is_generator {
get_generator_stacksize!($variant)
} else {
0
},
exception_table,
allow_zero!($variant, $code)
)
.expect("Must be valid"),
$code.stacksize
);
for constant in &$code.consts {
if let &$module::code_objects::Constant::CodeObject(
ref const_code,
) = constant
{
recursive_code_object(&pyc_editor::CodeObject::$variant(
const_code.clone(),
));
}
}
}};
}
handle_code_object_versions!(code, recursive_version);
}
macro_rules! call_recursive {
($variant:ident, $module:ident, $pyc:expr) => {{
recursive_code_object(&pyc_editor::CodeObject::$variant(
$pyc.code_object.clone(),
));
}};
}
handle_pyc_versions!(parsed_pyc, call_recursive, immutable);
}
});
});
}
#[test]
fn test_create_cfg_standard_lib() {
use pyc_editor::cfg::BlockIndex;
use pyc_editor::cfg::create_cfg;
use pyc_editor::cfg::create_mapped_cfg;
fn get_reachable_block_count<I: GenericInstruction>(cfg: &ControlFlowGraph<I>) -> usize {
let mut visited = vec![false; cfg.blocks.len()];
let mut stack = Vec::new();
if let Some(BlockIndex::Index(start)) = cfg.start_index.get_block_index() {
visited[*start] = true;
stack.push(*start);
} else {
return 0;
}
while let Some(index) = stack.pop() {
let block = &cfg.blocks[index];
for successor in [block.get_branch_block(), block.get_default_block()] {
if let Some(BlockIndex::Index(next)) = successor.get_block_index()
&& !visited[*next]
{
visited[*next] = true;
stack.push(*next);
}
}
}
visited.iter().filter(|&&reachable| reachable).count()
}
fn get_len_without_cache<I: GenericInstruction>(instructions: &[I]) -> usize {
instructions
.iter()
.filter(|i| !i.is_cache() && !i.is_extended_arg())
.count()
}
fn count_cfg_instructions<I: GenericInstruction>(cfg: &ControlFlowGraph<I>) -> usize {
let mut instruction_count = 0;
for block in &cfg.blocks {
instruction_count +=
get_len_without_cache(block.get_instructions_slice().unwrap_or_default());
if let BlockIndexInfo::Edge(BranchEdge { reason, .. }) = &block.get_branch_block()
&& reason.is_opcode()
{
instruction_count += 1;
}
}
instruction_count
}
fn no_map_overlap(
map: InstructionIndexMap,
total_instruction_count: usize,
could_have_deadcode: bool,
cache_after_jump_indexes: Vec<usize>,
) -> Result<(), String> {
let mut current_instruction_index = 0;
let ranges = map.get_cfg_ranges();
if !ranges.is_empty() {
for entry in map.get_cfg_ranges() {
if entry.start_instruction_index != current_instruction_index
&& !could_have_deadcode
{
return Err(format!(
"Start instruction index doesn't match: {} != {}",
entry.start_instruction_index, current_instruction_index
));
} else {
current_instruction_index += entry.instruction_length;
while cache_after_jump_indexes.contains(¤t_instruction_index) {
current_instruction_index += 1;
}
}
}
(current_instruction_index == total_instruction_count || could_have_deadcode)
.then_some(())
.ok_or(format!(
"Total instruction count doesn't match: {} != {}",
current_instruction_index, total_instruction_count
))
} else if total_instruction_count == 0 {
Ok(())
} else {
Err("No ranges found, despite expected empty instruction count".to_string())
}
}
fn check_opcode_map_match<I: GenericInstruction>(
instructions: &[I],
map: InstructionIndexMap,
cfg: ControlFlowGraph<I>,
) -> Result<(), String>
where
<<I::Opcode as GenericOpcode>::BranchReason as BranchReasonTrait>::Opcode:
PartialEq<I::Opcode>,
{
for (i, instruction) in instructions.iter().enumerate() {
let cfg_index = map.get_cfg_index(i);
match cfg_index {
CFGInstructionIndex::NoIndex => {}
CFGInstructionIndex::BlockIndex(block_index, instruction_index) => {
let block = cfg.blocks.get(block_index).unwrap();
let cfg_instruction = block
.get_instructions_slice()
.unwrap()
.get(instruction_index)
.unwrap_or_else(|| {
dbg!(
instruction_index,
&block,
i,
map.get_cfg_ranges(),
&cfg.blocks
);
panic!()
});
if cfg_instruction != instruction {
return Err(format!(
"original instruction doesn't match CFG instruction: {:#?} != {:#?}",
cfg_instruction, instruction,
));
}
}
CFGInstructionIndex::EdgeIndex(block_index) => {
let block = cfg.blocks.get(block_index).unwrap();
if let BlockIndexInfo::Edge(BranchEdge { reason, .. }) =
block.get_branch_block()
{
let opcode = reason.get_opcode();
if let Some(opcode) = opcode
&& *opcode != instruction.get_opcode() {
return Err(format!(
"original branch opcode doesn't match CFG opcode: {:#?} != {:#?}",
opcode,
instruction.get_opcode(),
));
}
} else {
return Err(format!(
"expected block {} to be a branch block with opcode",
block_index
));
}
}
}
}
Ok(())
}
LOGGER_INIT.call_once(|| {
common::setup();
env_logger::init();
});
common::PYTHON_VERSIONS.par_iter().for_each(|version| {
println!("Testing with Python version: {}", version);
let pyc_files = common::find_pyc_files(version);
pyc_files.par_iter().for_each(|pyc_file| {
println!("Testing pyc file: {:?}", pyc_file);
let file = std::fs::File::open(pyc_file).expect("Failed to open pyc file");
let reader = BufReader::new(file);
let parsed_pyc = load_pyc(reader).unwrap();
fn create_cfg_from_code(code: pyc_editor::CodeObject) {
macro_rules! create_cfg {
(V310, $code_clone:ident) => {
create_cfg(&$code_clone.code, None)
};
($variant:ident, $code_clone:ident) => {
create_cfg(
&$code_clone.code,
Some(&$code_clone.exception_table().unwrap()),
)
};
}
macro_rules! create_mapped_cfg {
(V310, $code_clone:ident) => {
{
let resolved_instructions = $code_clone.code.to_resolved(None).unwrap().0;
let (map, ext_cfg) = create_mapped_cfg(&resolved_instructions, None).unwrap();
(resolved_instructions, map, ext_cfg)
}
};
($variant:ident, $code_clone:ident) => {
{
let exception_table = $code_clone.exception_table().unwrap();
let (resolved_instructions, new_exception_table) = $code_clone.code.to_resolved(Some(&exception_table)).unwrap();
let (map, ext_cfg) = create_mapped_cfg(
&resolved_instructions,
new_exception_table.as_deref(),
).unwrap();
(resolved_instructions, map, ext_cfg)
}
};
}
macro_rules! has_exceptions {
(V310, $code:ident) => {
$code.code.iter().any(|v| matches!(v, crate::v310::instructions::Instruction::SetupFinally(_) | crate::v310::instructions::Instruction::SetupWith(_) | crate::v310::instructions::Instruction::SetupAsyncWith(_) ))
};
($variant:ident, $code:ident) => {
!$code.exception_table().unwrap().is_empty()
}
}
macro_rules! cfg_from_instructions {
($variant:ident, $module:ident, $code:expr) => {{
let code_clone = $code.clone();
let cfg = create_cfg!($variant, code_clone).unwrap();
let (resolved_instructions, map, ext_cfg) = create_mapped_cfg!($variant, code_clone);
let has_exceptions = has_exceptions!($variant, code_clone);
let mut cache_after_jump_indexes = vec![];
let mut is_jump = None;
for (i, instr) in resolved_instructions.iter().enumerate() {
if instr.is_jump() && !instr.is_conditional_jump() {
is_jump = Some(i);
} else if instr.is_cache() && is_jump.is_some() {
cache_after_jump_indexes.push(i)
} else {
is_jump = None
}
}
if let Err(error) = no_map_overlap(map.clone(), resolved_instructions.len(), has_exceptions, cache_after_jump_indexes.clone()) {
for range in map.get_cfg_ranges() {
dbg!(range);
dbg!(ext_cfg.blocks.get(range.block_index));
}
dbg!(&resolved_instructions);
dbg!(&code_clone.code);
println!("{}", ext_cfg.make_dot_graph());
dbg!(&cache_after_jump_indexes);
Err(error).unwrap()
}
for range in map.get_cfg_ranges() {
let condition = range.has_branch_instruction == matches!(ext_cfg.blocks.get(range.block_index).unwrap().get_branch_block(), BlockIndexInfo::Edge(BranchEdge { .. }));
if !condition {
dbg!(range, ext_cfg.blocks.get(range.block_index).unwrap());
dbg!(&resolved_instructions);
}
assert!(condition);
}
check_opcode_map_match(&resolved_instructions, map, ext_cfg).unwrap();
let instruction_len = get_len_without_cache(&code_clone.code);
if count_cfg_instructions(&cfg) > instruction_len {
dbg!(&cfg);
dbg!(&code_clone.code);
}
assert!(count_cfg_instructions(&cfg) <= instruction_len);
assert_eq!(get_reachable_block_count(&cfg), cfg.blocks.len());
for constant in code_clone.consts {
if let $module::code_objects::Constant::CodeObject(const_code) =
constant
{
create_cfg_from_code(pyc_editor::CodeObject::$variant(
const_code.clone(),
));
}
}
}};
}
handle_code_object_versions!(code, cfg_from_instructions)
}
macro_rules! create_cfgs {
($variant:ident, $module:ident, $pyc:expr) => {{
create_cfg_from_code(pyc_editor::CodeObject::$variant(
$pyc.code_object.clone(),
));
}};
}
handle_pyc_versions!(parsed_pyc, create_cfgs, immutable);
});
});
}
#[cfg(feature = "sir")]
#[test]
fn test_create_sir_standard_lib() {
use pyc_editor::cfg::{create_cfg, simple_cfg_to_ext_cfg};
use pyc_editor::sir::cfg_to_ir;
LOGGER_INIT.call_once(|| {
common::setup();
env_logger::init();
});
common::PYTHON_VERSIONS.par_iter().for_each(|version| {
println!("Testing with Python version: {}", version);
let pyc_files = common::find_pyc_files(version);
pyc_files.par_iter().for_each(|pyc_file| {
println!("Testing pyc file: {:?}", pyc_file);
let file = std::fs::File::open(pyc_file).expect("Failed to open pyc file");
let reader = BufReader::new(file);
let parsed_pyc = load_pyc(reader).unwrap();
fn create_cfg_from_code(code: pyc_editor::CodeObject) {
macro_rules! create_cfg {
(V310, $code_clone:ident) => {
create_cfg(&$code_clone.code, None)
};
($variant:ident, $code_clone:ident) => {
create_cfg(
&$code_clone.code,
Some(&$code_clone.exception_table().unwrap()),
)
};
}
macro_rules! cfg_from_instructions {
($variant:ident, $module:ident, $code:expr) => {{
let code_clone = $code.clone();
let cfg = create_cfg!($variant, code_clone).unwrap();
let cfg = simple_cfg_to_ext_cfg(&cfg).unwrap();
let is_generator = $code.flags.intersects(
CodeFlags::GENERATOR
| CodeFlags::COROUTINE
| CodeFlags::ASYNC_GENERATOR
| CodeFlags::ITERABLE_COROUTINE,
);
cfg_to_ir::<_, pyc_editor::$module::opcodes::sir::SIRNode>(
&cfg,
if is_generator {
get_generator_stacksize!($variant) == 1
} else {
false
},
)
.unwrap();
for constant in code_clone.consts {
if let $module::code_objects::Constant::CodeObject(const_code) =
constant
{
create_cfg_from_code(pyc_editor::CodeObject::$variant(
const_code.clone(),
));
}
}
}};
}
handle_code_object_versions!(code, cfg_from_instructions)
}
macro_rules! create_cfgs {
($variant:ident, $module:ident, $pyc:expr) => {{
create_cfg_from_code(pyc_editor::CodeObject::$variant(
$pyc.code_object.clone(),
));
}};
}
handle_pyc_versions!(parsed_pyc, create_cfgs, immutable);
});
});
}
#[test]
fn test_jump_target_standard_lib() {
LOGGER_INIT.call_once(|| {
common::setup();
env_logger::init();
});
common::PYTHON_VERSIONS.par_iter().for_each(|version| {
println!("Testing with Python version: {}", version);
let pyc_files = common::find_pyc_files(version);
pyc_files.par_iter().for_each(|pyc_file| {
println!("Testing pyc file: {:?}", pyc_file);
let file = std::fs::File::open(pyc_file).expect("Failed to open pyc file");
let reader = BufReader::new(file);
let parsed_pyc = load_pyc(reader).unwrap();
fn check_jump_targets(code: pyc_editor::CodeObject) {
macro_rules! cfg_from_instructions {
($variant:ident, $module:ident, $code:expr) => {{
let jump_map = $code.code.get_jump_map();
for target in jump_map.values() {
assert!(!$code.code.get(*target as usize).unwrap().is_cache());
}
for constant in $code.consts {
if let $module::code_objects::Constant::CodeObject(const_code) =
constant
{
check_jump_targets(pyc_editor::CodeObject::$variant(
const_code.clone(),
));
}
}
}};
}
handle_code_object_versions!(code, cfg_from_instructions)
}
macro_rules! create_cfgs {
($variant:ident, $module:ident, $pyc:expr) => {{
check_jump_targets(pyc_editor::CodeObject::$variant($pyc.code_object.clone()));
}};
}
handle_pyc_versions!(parsed_pyc, create_cfgs, immutable);
});
});
}
#[test]
#[ignore = "This test will write the files to disk so we can run the Python tests on them. That way we're sure the files are correct."]
fn test_write_standard_lib() {
LOGGER_INIT.call_once(|| {
common::setup();
env_logger::init();
});
common::PYTHON_VERSIONS.par_iter().for_each(|version| {
println!("Testing with Python version: {}", version);
let pyc_files = common::find_pyc_files(version);
pyc_files.par_iter().for_each(|pyc_file| {
println!("Testing pyc file: {:?}", pyc_file);
let file = std::fs::File::open(pyc_file).expect("Failed to open pyc file");
let mut reader = BufReader::new(file);
let pyc = load_pyc(&mut reader).expect("Failed to read pyc file");
let output_dir = get_custom_path(pyc_file.parent().unwrap(), version, "rewritten")
.parent()
.unwrap()
.to_path_buf();
std::fs::create_dir_all(&output_dir).expect("Failed to create output directory");
let output_path = Path::new(&output_dir).join(pyc_file.file_name().unwrap());
let mut output_file =
std::fs::File::create(&output_path).expect("Failed to create output file");
output_file
.write_all(&dump_pyc(pyc).expect("Failed to dump pyc file"))
.unwrap_or_else(|_| panic!("Failed to write to {:?}", output_path));
});
});
}
fn get_custom_path(original_path: &Path, version: &PyVersion, prefix: &'static str) -> PathBuf {
let relative_path = original_path
.strip_prefix(
Path::new(DATA_PATH).join(format!("cpython-{}.{}/Lib", version.major, version.minor)),
)
.unwrap();
Path::new(DATA_PATH)
.join(format!("{prefix}-{version}/Lib"))
.join(relative_path)
}