use std::{
collections::{HashMap, VecDeque},
fmt::Debug,
hash::BuildHasherDefault,
ops::Deref,
};
#[cfg(feature = "dot")]
use crate::utils::BlockKind;
use crate::{
error::Error,
traits::{
BlockSliceExt, BranchReasonTrait, ExtInstructionAccess, GenericInstruction, GenericOpcode,
InstructionAccess, SimpleInstructionAccess,
},
utils::ExceptionTableEntry,
};
#[cfg(feature = "sir")]
use crate::{
sir::SIRBranchEdge,
traits::{GenericSIRNode, SIROwned},
};
#[cfg(feature = "dot")]
use petgraph::{
dot::{Config, Dot},
graph::NodeIndex,
};
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum BlockIndex {
/// Index of the block in the `blocks` list of the CFG
Index(usize),
/// For jumps with invalid jump targets (the value is the invalid jump index)
InvalidIndex(usize),
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum BlockIndexInfo<BranchReason>
where
BranchReason: BranchReasonTrait,
{
Edge(BranchEdge<BranchReason>),
/// For blocks that fallthrough with no opcode (cannot be generated by Python, used by internal algorithms)
Fallthrough(BlockIndex),
/// For blocks without a target
NoIndex,
}
#[cfg(feature = "sir")]
impl<BranchReason: BranchReasonTrait> BlockIndexInfo<BranchReason> {
pub fn into_sir<SIRNode>(
&self,
statements: Option<Vec<crate::sir::SIRStatement<SIRNode>>>,
) -> crate::sir::SIRBlockIndexInfo<SIRNode>
where
SIRNode: GenericSIRNode,
crate::sir::SIR<SIRNode>: SIROwned<SIRNode>,
<SIRNode as GenericSIRNode>::Opcode: GenericOpcode<BranchReason = BranchReason>,
{
match &self {
BlockIndexInfo::Edge(edge) => crate::sir::SIRBlockIndexInfo::Edge(SIRBranchEdge {
reason: edge.reason.clone(),
statements: statements.map(|v| crate::sir::SIR::new(v)),
block_index: edge.block_index.clone(),
}),
BlockIndexInfo::Fallthrough(block_index) => {
crate::sir::SIRBlockIndexInfo::Fallthrough(block_index.clone())
}
BlockIndexInfo::NoIndex => crate::sir::SIRBlockIndexInfo::NoIndex,
}
}
}
impl<BranchReason> BlockIndexInfo<BranchReason>
where
BranchReason: BranchReasonTrait,
{
pub fn get_block_index(&self) -> Option<&BlockIndex> {
match self {
BlockIndexInfo::Edge(BranchEdge { block_index, .. }) => Some(block_index),
BlockIndexInfo::Fallthrough(block_index) => Some(block_index),
BlockIndexInfo::NoIndex => None,
}
}
}
/// Used to represent the opcode that was used for this branch and the block index it's jumping to.
/// We do this so the value of the branch instruction cannot represent a wrong index.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct BranchEdge<BranchReason>
where
BranchReason: BranchReasonTrait,
{
pub reason: BranchReason,
pub block_index: BlockIndex,
}
#[derive(Debug, PartialEq, Clone)]
/// Represents a normal block in the control flow graph
pub struct NormalBlock<I: GenericInstruction> {
pub instructions: Vec<I>,
/// Index to block for conditional jump
pub branch_block: BlockIndexInfo<<I::Opcode as GenericOpcode>::BranchReason>,
/// Index to default block (unconditional)
pub default_block: BlockIndexInfo<<I::Opcode as GenericOpcode>::BranchReason>,
}
#[derive(Debug, PartialEq, Clone)]
/// Represents an exception block in the control flow graph
/// This includes a list of block indexes that belong to this exception block
pub struct ExceptionBlock<I: GenericInstruction> {
pub block_indexes: Vec<usize>,
/// Index to the exception handler block
pub exception_handler: BranchEdge<<I::Opcode as GenericOpcode>::BranchReason>,
/// Index to default block
pub default_block: BlockIndexInfo<<I::Opcode as GenericOpcode>::BranchReason>,
}
#[derive(Debug, PartialEq, Clone)]
pub enum Block<I: GenericInstruction> {
NormalBlock(NormalBlock<I>),
ExceptionBlock(ExceptionBlock<I>),
}
impl<I: GenericInstruction> Block<I> {
pub fn get_branch_block(&self) -> BlockIndexInfo<<I::Opcode as GenericOpcode>::BranchReason> {
match self {
Block::NormalBlock(block) => block.branch_block.clone(),
Block::ExceptionBlock(block) => BlockIndexInfo::Edge(block.exception_handler.clone()),
}
}
pub fn get_default_block(&self) -> BlockIndexInfo<<I::Opcode as GenericOpcode>::BranchReason> {
match self {
Block::NormalBlock(block) => block.default_block.clone(),
Block::ExceptionBlock(block) => block.default_block.clone(),
}
}
pub fn get_instructions_mut(&mut self) -> Option<&mut Vec<I>> {
match self {
Block::NormalBlock(block) => Some(&mut block.instructions),
Block::ExceptionBlock(_) => None,
}
}
pub fn get_instructions(self) -> Option<Vec<I>> {
match self {
Block::NormalBlock(block) => Some(block.instructions),
Block::ExceptionBlock(_) => None,
}
}
pub fn get_instructions_slice(&self) -> Option<&[I]> {
match self {
Block::NormalBlock(block) => Some(&block.instructions),
Block::ExceptionBlock(_) => None,
}
}
}
impl<T: GenericInstruction> NormalBlock<T> {
pub fn is_terminating(&self) -> bool {
matches!(self.default_block, BlockIndexInfo::NoIndex)
}
/// Whether the block has a conditional jump or not
pub fn is_conditional(&self) -> bool {
matches!(
self.branch_block,
BlockIndexInfo::Edge(BranchEdge {
block_index: BlockIndex::Index(_),
..
})
)
}
}
#[derive(Debug, PartialEq)]
pub struct ControlFlowGraph<I>
where
I: GenericInstruction,
{
pub blocks: Vec<Block<I>>,
pub start_index: BlockIndexInfo<<I::Opcode as GenericOpcode>::BranchReason>,
}
impl<I: GenericInstruction> BlockSliceExt<I> for [Block<I>] {
fn find_exception_block(&self, index_to_search: usize) -> Option<usize> {
for (i, block) in self.iter().enumerate() {
match block {
Block::ExceptionBlock(block) => {
if block.block_indexes.contains(&index_to_search) {
return Some(i);
}
}
_ => continue,
}
}
None
}
}
impl<I: GenericInstruction> ControlFlowGraph<I> {
pub fn find_exception_block(&self, index_to_search: usize) -> Option<usize> {
self.blocks.find_exception_block(index_to_search)
}
}
#[cfg(feature = "dot")]
impl<I> ControlFlowGraph<I>
where
I: GenericInstruction,
{
pub fn make_dot_graph(&self) -> String {
let mut graph = petgraph::Graph::<(BlockKind, String), String>::new();
Self::add_block(
&mut graph,
&self.blocks,
self.start_index.get_block_index().cloned(),
&mut HashMap::new(),
);
format!(
"{:#?}",
Dot::with_attr_getters(
&graph,
&[Config::NodeNoLabel],
&|_, e| {
let color = if e.weight() != "fallthrough" {
"green"
} else {
"red"
};
format!("shape=rect, color={}", color)
},
&|_, (_, (kind, s))| {
let label = s.replace("\n", r"\l");
match kind {
BlockKind::NormalBlock => {
format!(r#"shape=rect, label="{}""#, label)
}
BlockKind::ExceptionBlock => {
format!(
r#"shape=rect, style=filled, fillcolor=orange, label="{}""#,
label
)
}
BlockKind::InExceptionRange => {
format!(r#"shape=rect, color=orange, label="{}""#, label)
}
}
},
)
)
}
fn add_block(
graph: &mut petgraph::Graph<(BlockKind, String), String>,
blocks: &[Block<I>],
block_index: Option<BlockIndex>,
block_map: &mut HashMap<Option<BlockIndex>, NodeIndex>,
) -> Option<NodeIndex> {
let (block, index) = match &block_index {
Some(BlockIndex::Index(index)) => (blocks.get(*index).unwrap(), index),
_ => return None,
};
let text = match block {
Block::NormalBlock(block) => {
let mut lines = block
.instructions
.iter()
.map(|i| format!("{:#?} {:#?}", i.get_opcode(), i.get_raw_value()))
.collect::<Vec<_>>();
if let BlockIndexInfo::Edge(BranchEdge { reason, .. }) = &block.branch_block {
lines.push(format!("{}", reason));
}
lines.join("\n")
}
Block::ExceptionBlock(block) => {
let BranchEdge { reason, .. } = &block.exception_handler;
format!("{}", reason)
}
};
let kind = match block {
Block::NormalBlock(_) => {
if blocks.find_exception_block(*index).is_some() {
BlockKind::InExceptionRange
} else {
BlockKind::NormalBlock
}
}
Block::ExceptionBlock(_) => BlockKind::ExceptionBlock,
};
let index = if let std::collections::hash_map::Entry::Vacant(e) =
block_map.entry(block_index.clone())
{
let index = graph.add_node((kind.clone(), text));
e.insert(index);
index
} else {
block_map[&block_index]
};
let (branch_index, reason) = match block {
Block::NormalBlock(block) => match &block.branch_block {
BlockIndexInfo::Edge(BranchEdge {
block_index: branch_index,
reason,
}) => (Some(branch_index), Some(reason.clone())),
_ => (None, None),
},
Block::ExceptionBlock(block) => {
let BranchEdge {
block_index: branch_index,
reason,
} = &block.exception_handler;
(Some(branch_index), Some(reason.clone()))
}
};
let branch_index = branch_index.cloned();
let branch_index = if block_map.contains_key(&branch_index) {
Some(block_map[&branch_index])
} else {
let index = Self::add_block(graph, blocks, branch_index.clone(), block_map);
if let Some(index) = index {
block_map.insert(branch_index, index);
Some(index)
} else {
match &branch_index {
Some(BlockIndex::InvalidIndex(invalid_index)) => Some(graph.add_node((
kind.clone(),
format!("invalid jump to index {}", invalid_index),
))),
Some(BlockIndex::Index(_)) => unreachable!(),
None => None,
}
}
};
let default_block_index = block.get_default_block().get_block_index().cloned();
let default_index = if block_map.contains_key(&default_block_index) {
Some(block_map[&default_block_index])
} else {
let index = Self::add_block(graph, blocks, default_block_index.clone(), block_map);
if let Some(index) = index {
block_map.insert(default_block_index, index);
Some(index)
} else {
match &default_block_index {
Some(BlockIndex::InvalidIndex(invalid_index)) => Some(
graph.add_node((kind, format!("invalid jump to index {}", invalid_index))),
),
Some(BlockIndex::Index(_)) => unreachable!(),
None => None,
}
}
};
if let Some(to_index) = branch_index {
let text = format!("{}", reason.unwrap());
graph.add_edge(index, to_index, text);
}
if let Some(to_index) = default_index {
graph.add_edge(index, to_index, "fallthrough".to_string());
}
Some(index)
}
}
impl<I, T> SimpleInstructionAccess<I> for T where
T: Deref<Target = [I]> + AsRef<[I]> + InstructionAccess<u8, I>
{
}
/// This "fixes" a unique pattern:
///
/// LOAD_CONST 0
/// POP_JUMP_FORWARD_IF_TRUE 1
/// EXTENDED_ARG
/// STORE_NAME 0
///
/// We will convert this to
///
/// LOAD_CONST 0
/// POP_JUMP_FORWARD_IF_TRUE 3
/// EXTENDED_ARG
/// STORE_NAME 0
/// JUMP_FORWARD 1
/// STORE_NAME 0
///
/// `blocks_to_fix` contains a list of blocks indexes that jump over an EXTENDED_ARG
/// TODO: This fix doesn't work if the target instruction is another jump
fn fix_extended_args<I>(cfg: &mut ControlFlowGraph<I>, blocks_to_fix: &[BlockIndex])
where
I: GenericInstruction,
{
for block_to_fix in blocks_to_fix {
match block_to_fix {
BlockIndex::Index(index) => {
let default_block_index = match &cfg.blocks[*index] {
Block::NormalBlock(block) => match block.default_block {
BlockIndexInfo::Edge(BranchEdge {
block_index: BlockIndex::Index(default_index),
..
}) => default_index,
_ => unreachable!(),
},
Block::ExceptionBlock(_) => continue,
};
let branch_block_index = match &cfg.blocks[*index] {
Block::NormalBlock(block) => match block.branch_block {
BlockIndexInfo::Edge(BranchEdge {
block_index: BlockIndex::Index(branch_index),
..
}) => branch_index,
_ => unreachable!(),
},
Block::ExceptionBlock(_) => continue,
};
// The instruction that the EXTENDED_ARG should be applied to is at the start of this one
let mut instructions = cfg.blocks[branch_block_index]
.clone()
.get_instructions()
.unwrap();
// If there are multiple extended args, this should indicate that
let instructions_to_copy = instructions
.iter()
.take_while(|i| i.is_extended_arg())
.count()
// The instruction itself
+ 1;
// Remove rest of instructions in the branch block
*cfg.blocks
.get_mut(branch_block_index)
.expect("index is valid here")
.get_instructions_mut()
.unwrap() = instructions
.iter()
.take(instructions_to_copy)
.cloned()
.collect();
// Copy the instruction(s) behind the EXTENDED_ARG
cfg.blocks
.get_mut(default_block_index)
.expect("index is valid here")
.get_instructions_mut()
.unwrap()
.extend_from_slice(
&instructions
.iter()
.take(instructions_to_copy)
.cloned()
.collect::<Vec<_>>(),
);
// Remove first instruction(s) in the branch block that we will copy into the new branch block
let instructions = if !instructions.is_empty() {
// Can be empty if we're jumping to another branch instruction (which is not included in the basic block)
// This does not fix it yet, but it's a WIP
instructions.split_off(instructions_to_copy)
} else {
instructions
};
// Create new block that has the remaining instructions of the branch block
cfg.blocks.push(Block::NormalBlock(NormalBlock {
instructions,
branch_block: cfg.blocks[branch_block_index].get_branch_block().clone(),
default_block: cfg.blocks[branch_block_index].get_default_block().clone(),
}));
let new_block_index = cfg.blocks.len() - 1;
match cfg
.blocks
.get_mut(branch_block_index)
.expect("index is valid here")
{
Block::NormalBlock(block) => {
block.default_block =
BlockIndexInfo::Fallthrough(BlockIndex::Index(new_block_index));
block.branch_block = BlockIndexInfo::NoIndex;
}
Block::ExceptionBlock(_) => unreachable!(),
}
match cfg
.blocks
.get_mut(default_block_index)
.expect("index is valid here")
{
Block::NormalBlock(block) => {
block.default_block =
BlockIndexInfo::Fallthrough(BlockIndex::Index(new_block_index));
}
Block::ExceptionBlock(_) => unreachable!(),
}
// TODO: Add correct info
// Should never happen but just in case we will add a NOP.
if let Block::NormalBlock(new_block) = cfg.blocks.get_mut(new_block_index).unwrap()
&& new_block.instructions.is_empty()
{
new_block.instructions.push(I::get_nop());
}
}
_ => unreachable!(),
}
}
}
/// Exception table should be passed for 3.11+
pub fn create_cfg<I>(
instructions: Vec<I>,
exception_table: Option<Vec<ExceptionTableEntry>>,
) -> Result<ControlFlowGraph<I>, Error>
where
I: GenericInstruction,
Vec<I>: InstructionAccess<I::OpargType, I>,
<I::Opcode as GenericOpcode>::BranchReason: BranchReasonTrait<Opcode = I::Opcode>,
{
// Used for keeping track of finished blocks
let mut temp_blocks: nohash_hasher::IntMap<usize, Block<I>> =
nohash_hasher::IntMap::with_capacity_and_hasher(
instructions.len().div_ceil(5),
BuildHasherDefault::default(),
);
enum BlockState {
BlockIndexAssigned(BlockIndex),
BlockProcessed(BlockIndex),
}
impl BlockState {
pub fn get_index(&self) -> &BlockIndex {
match &self {
BlockState::BlockIndexAssigned(index) | BlockState::BlockProcessed(index) => index,
}
}
}
// Maps instruction index to block index
let mut block_map: nohash_hasher::IntMap<usize, BlockState> =
nohash_hasher::IntMap::with_capacity_and_hasher(
temp_blocks.capacity(),
BuildHasherDefault::default(),
);
enum ExceptionState {
PrevBlockPopped,
EmptyBlockCreated,
Finished,
}
// Keeps track of which exception indexes we already processed
let mut exceptions_processed: nohash_hasher::IntMap<usize, ExceptionState> =
nohash_hasher::IntMap::with_capacity_and_hasher(
exception_table
.as_ref()
.map(|table| table.len())
.unwrap_or(0),
BuildHasherDefault::default(),
);
let jump_map = instructions.get_jump_map();
let exception_map: nohash_hasher::IntMap<u32, &ExceptionTableEntry> =
if let Some(exception_table) = &exception_table {
exception_table.iter().map(|e| (e.start, e)).collect()
} else {
nohash_hasher::IntMap::default()
};
// End indexes
let exception_ends = if let Some(exception_table) = &exception_table {
exception_table.iter().map(|e| e.end).collect()
} else {
vec![]
};
let all_jump_targets: nohash_hasher::IntSet<u32> = jump_map
.values()
.chain(
exception_map
.values()
.map(|ExceptionTableEntry { target, .. }| target),
)
.chain(exception_ends.iter())
.cloned()
.collect();
struct QueueEntry {
/// This instruction index starts a new block
instruction_index: usize,
/// This is the block index the new block should get
block_index: usize,
}
// Keeps indexes of instructions that start new blocks and still need to be processed with their corresponding block index they should get.
let mut block_queue: VecDeque<QueueEntry> = VecDeque::new();
fn add_block_to_queue(
instruction_index: usize,
block_index: usize,
block_map: &mut nohash_hasher::IntMap<usize, BlockState>,
block_queue: &mut VecDeque<QueueEntry>,
) {
if let std::collections::hash_map::Entry::Vacant(entry) = block_map.entry(instruction_index)
{
entry.insert(BlockState::BlockIndexAssigned(BlockIndex::Index(
block_index,
)));
};
block_queue.push_front(QueueEntry {
instruction_index,
block_index,
});
}
add_block_to_queue(0, 0, &mut block_map, &mut block_queue);
// A list of block indexes that will be used to fix a unique EXTENDED_ARG pattern. See `fix_extended_args`.
let mut block_indexes_to_fix = vec![];
let mut curr_block_index = 0;
while !block_queue.is_empty() {
let QueueEntry {
mut instruction_index,
block_index,
} = block_queue.pop_back().expect("queue is not empty");
let mut curr_block = vec![];
let start_index = instruction_index;
macro_rules! block_exists {
($instruction_index:expr, is_curr_instruction) => {
(block_map.contains_key(&$instruction_index) && ($instruction_index != start_index))
|| (matches!(
block_map.get(&$instruction_index),
Some(BlockState::BlockProcessed(_))
))
};
($instruction_index:expr) => {
block_map.contains_key(&$instruction_index)
};
}
#[derive(Debug)]
enum ExitReason {
BlockExists,
IsJumpTarget,
DoesJump,
StopsExecution,
ExceptionStart,
EmptyBlockCreated,
}
let exit_reason = loop {
let exception_start = exception_map.get(&(instruction_index as u32));
if exception_start.is_some()
&& !exceptions_processed.contains_key(&instruction_index)
&& !block_exists!(instruction_index, is_curr_instruction)
{
let block_index = if !curr_block.is_empty() {
// Push current block that falls through to the empty exception block
curr_block_index += 1;
temp_blocks.insert(
block_index,
Block::NormalBlock(NormalBlock {
instructions: curr_block,
branch_block: BlockIndexInfo::NoIndex,
default_block: BlockIndexInfo::Fallthrough(BlockIndex::Index(
curr_block_index,
)),
}),
);
curr_block_index
} else {
block_index
};
add_block_to_queue(
instruction_index,
block_index,
&mut block_map,
&mut block_queue,
);
exceptions_processed.insert(instruction_index, ExceptionState::PrevBlockPopped);
break ExitReason::ExceptionStart;
} else if block_exists!(instruction_index, is_curr_instruction) {
if !curr_block.is_empty() {
// encountered a jump target while processing a block
if let Some(exception_state) = exceptions_processed.get(&instruction_index) {
match exception_state {
ExceptionState::Finished => break ExitReason::BlockExists,
_ => {
if instruction_index != start_index {
// Another block already in the queue will handle it in the future
temp_blocks.insert(
block_index,
Block::NormalBlock(NormalBlock {
instructions: curr_block,
branch_block: BlockIndexInfo::NoIndex,
default_block: BlockIndexInfo::Fallthrough(
block_map[&instruction_index].get_index().clone(),
),
}),
);
break ExitReason::BlockExists;
}
}
}
} else {
temp_blocks.insert(
block_index,
Block::NormalBlock(NormalBlock {
instructions: curr_block,
branch_block: BlockIndexInfo::NoIndex,
default_block: BlockIndexInfo::Fallthrough(
block_map[&instruction_index].get_index().clone(),
),
}),
);
break ExitReason::BlockExists;
}
}
} else if all_jump_targets.contains(&(instruction_index as u32))
&& !curr_block.is_empty()
{
// If this is a jump target, place it in a new block (used to support backwards jumps)
curr_block_index += 1;
temp_blocks.insert(
block_index,
Block::NormalBlock(NormalBlock {
instructions: curr_block,
branch_block: BlockIndexInfo::NoIndex,
default_block: BlockIndexInfo::Fallthrough(BlockIndex::Index(
curr_block_index,
)),
}),
);
add_block_to_queue(
instruction_index,
curr_block_index,
&mut block_map,
&mut block_queue,
);
break ExitReason::IsJumpTarget;
}
let instruction = instructions[instruction_index].clone();
if let Some(exception_entry) = exception_start
&& let Some(ExceptionState::PrevBlockPopped) =
exceptions_processed.get(&instruction_index)
{
// Empty block for the exception start, jumps to exception target and falls through to the exception start index
temp_blocks.insert(
block_index,
Block::ExceptionBlock(ExceptionBlock {
block_indexes: vec![],
exception_handler: BranchEdge {
reason: <I::Opcode as GenericOpcode>::BranchReason::from_exception(
exception_entry.lasti,
exception_entry.depth as usize,
)?,
block_index: if block_exists!(exception_entry.target as usize) {
block_map[&(exception_entry.target as usize)]
.get_index()
.clone()
} else if exception_entry.target < instructions.len() as u32 {
curr_block_index += 1;
add_block_to_queue(
exception_entry.target as usize,
curr_block_index,
&mut block_map,
&mut block_queue,
);
BlockIndex::Index(curr_block_index)
} else {
BlockIndex::InvalidIndex(exception_entry.target as usize)
},
},
default_block: {
curr_block_index += 1;
add_block_to_queue(
instruction_index,
curr_block_index,
&mut block_map,
&mut block_queue,
);
BlockIndexInfo::Edge(BranchEdge {
reason: <I::Opcode as GenericOpcode>::BranchReason::from_exception(
exception_entry.lasti,
exception_entry.depth as usize,
)?,
block_index: BlockIndex::Index(curr_block_index),
})
},
}),
);
exceptions_processed
.entry(instruction_index)
.and_modify(|v| {
*v = ExceptionState::EmptyBlockCreated;
});
break ExitReason::EmptyBlockCreated;
} else if instruction.is_jump() {
let next_instruction = if instruction_index + 1 < instructions.len()
&& instruction.is_conditional_jump()
{
Some(instruction_index + 1)
} else {
None
};
let jump_instruction =
if let Some(jump_index) = jump_map.get(&(instruction_index as u32)) {
if instruction.is_conditional_jump()
&& let Some(instruction) = instructions.get(*jump_index as usize - 1)
&& instruction.is_extended_arg()
{
block_indexes_to_fix.push(BlockIndex::Index(curr_block_index));
}
Some(*jump_index)
} else {
None
};
// Remove extended args from the end of curr_block (they're already calculated in the jump target)
while let Some(last) = curr_block.last() {
if last.is_extended_arg() {
curr_block.pop();
} else {
break;
}
}
temp_blocks.insert(
block_index,
Block::NormalBlock(NormalBlock {
instructions: curr_block,
branch_block: BlockIndexInfo::Edge(BranchEdge {
reason: <I::Opcode as GenericOpcode>::BranchReason::from_opcode(
instruction.get_opcode(),
)?,
block_index: if let Some(jump_index) = jump_instruction {
if block_exists!(jump_index as usize) {
block_map[&(jump_index as usize)].get_index().clone()
} else {
curr_block_index += 1;
add_block_to_queue(
jump_index as usize,
curr_block_index,
&mut block_map,
&mut block_queue,
);
BlockIndex::Index(curr_block_index)
}
} else {
BlockIndex::InvalidIndex(
instructions
.get_full_arg(instruction_index)
.expect("Index is within bounds")
as usize,
)
},
}),
default_block: if let Some(next_index) = next_instruction {
if block_exists!(next_index) {
BlockIndexInfo::Edge(BranchEdge {
reason:
<I::Opcode as GenericOpcode>::BranchReason::from_opcode(
instruction.get_opcode(),
)?,
block_index: block_map[&next_index].get_index().clone(),
})
} else {
curr_block_index += 1;
add_block_to_queue(
next_index,
curr_block_index,
&mut block_map,
&mut block_queue,
);
BlockIndexInfo::Edge(BranchEdge {
reason:
<I::Opcode as GenericOpcode>::BranchReason::from_opcode(
instruction.get_opcode(),
)?,
block_index: BlockIndex::Index(curr_block_index),
})
}
} else {
BlockIndexInfo::NoIndex
},
}),
);
break ExitReason::DoesJump;
}
curr_block.push(instruction.clone());
if instruction.stops_execution() {
temp_blocks.insert(
block_index,
Block::NormalBlock(NormalBlock {
instructions: curr_block,
branch_block: BlockIndexInfo::NoIndex,
default_block: BlockIndexInfo::NoIndex,
}),
);
break ExitReason::StopsExecution;
}
if instruction_index + 1 < instructions.len() {
instruction_index += 1;
} else {
temp_blocks.insert(
block_index,
Block::NormalBlock(NormalBlock {
instructions: curr_block,
branch_block: BlockIndexInfo::NoIndex,
default_block: BlockIndexInfo::NoIndex,
}),
);
break ExitReason::StopsExecution;
}
};
match exit_reason {
ExitReason::StopsExecution | ExitReason::DoesJump | ExitReason::EmptyBlockCreated => {
if let Some(BlockState::BlockIndexAssigned(target_block)) =
block_map.get(&start_index)
{
// Might already exist if it is at the start of an exception
*block_map.get_mut(&start_index).unwrap() =
BlockState::BlockProcessed(target_block.clone());
if let Some(ExceptionState::EmptyBlockCreated) =
exceptions_processed.get(&start_index)
&& !matches!(exit_reason, ExitReason::EmptyBlockCreated)
{
exceptions_processed.entry(start_index).and_modify(|v| {
*v = ExceptionState::Finished;
});
}
}
}
ExitReason::IsJumpTarget | ExitReason::ExceptionStart => {}
ExitReason::BlockExists => {}
}
}
let mut temp_blocks = temp_blocks.into_iter().collect::<Vec<_>>();
temp_blocks.sort_by_key(|(i, _)| *i);
let order_map: HashMap<usize, usize> = temp_blocks
.iter()
.enumerate()
.map(|(i, (index, _))| (*index, i))
.collect();
fn replace_block_index<BranchReason>(
block_index: &mut BlockIndexInfo<BranchReason>,
order_map: &HashMap<usize, usize>,
) where
BranchReason: BranchReasonTrait,
{
match block_index {
BlockIndexInfo::Edge(BranchEdge {
block_index: BlockIndex::Index(block_index),
..
}) => {
*block_index = order_map[block_index];
}
BlockIndexInfo::Fallthrough(BlockIndex::Index(block_index)) => {
*block_index = order_map[block_index];
}
_ => {}
};
}
if exception_table.is_some() {
// Fill the missing block indexes in the exception blocks
for (i, block_index) in block_map.iter() {
let block_index = match block_index.get_index() {
BlockIndex::Index(index) => index,
_ => continue,
};
let mut possible_exceptions = vec![];
for entry in exception_map.values() {
if entry.start as usize <= *i && entry.end as usize > *i {
let exception_block_index = match block_map.get(&(entry.start as usize)) {
Some(state) => match state.get_index() {
BlockIndex::Index(index) => index,
_ => continue,
},
// It can happen that the exception entry doesn't have a block if it's in deadcode and can't be reached. See `test_311_exception_handling_in_loop`.
_ => continue,
};
possible_exceptions.push((exception_block_index, entry));
}
}
possible_exceptions.sort_by_key(|(i, _)| **i);
// Choose the most inner exception range
if let Some((exception_block_index, entry)) = possible_exceptions.last() {
let (_, block) = temp_blocks.get_mut(**exception_block_index).unwrap();
match block {
Block::ExceptionBlock(exception_block) => {
let block_index = if *i == entry.start as usize {
// We need to point to the fallthrough block, not the empty exception block
match exception_block.default_block.get_block_index().unwrap() {
BlockIndex::Index(index) => index,
BlockIndex::InvalidIndex(_) => unreachable!(),
}
} else {
block_index
};
exception_block.block_indexes.push(*block_index);
}
_ => unreachable!(),
}
}
}
}
// Replace the block indexes with their new (ordered) indexes
for (_, block) in temp_blocks.iter_mut() {
match block {
Block::NormalBlock(block) => {
replace_block_index(&mut block.branch_block, &order_map);
replace_block_index(&mut block.default_block, &order_map);
}
Block::ExceptionBlock(block) => {
if let BranchEdge {
block_index: BlockIndex::Index(block_index),
..
} = &mut block.exception_handler
{
*block_index = order_map[block_index];
}
replace_block_index(&mut block.default_block, &order_map);
block
.block_indexes
.iter_mut()
.for_each(|i| *i = order_map[i]);
}
}
}
let blocks: Vec<Block<I>> = temp_blocks.into_iter().map(|(_, b)| b).collect();
let mut cfg = ControlFlowGraph::<I> {
start_index: if !blocks.is_empty() {
BlockIndexInfo::Fallthrough(BlockIndex::Index(0))
} else {
BlockIndexInfo::NoIndex
},
blocks,
};
fix_extended_args(&mut cfg, &block_indexes_to_fix);
Ok(cfg)
}
// Convert a cfg that consists of simple instructions to a cfg where the extended args are resolved.
pub fn simple_cfg_to_ext_cfg<SimpleI, ExtI, ExtInstructions>(
simple_cfg: &ControlFlowGraph<SimpleI>,
) -> Result<ControlFlowGraph<ExtI>, Error>
where
SimpleI: GenericInstruction<OpargType = u8, OtherType = ExtI>,
ExtI: GenericInstruction<
OpargType = u32,
Opcode = SimpleI::Opcode,
Instructions = ExtInstructions,
>,
ExtInstructions: ExtInstructionAccess<SimpleI, ExtI>,
{
let mut blocks = vec![];
for block in &simple_cfg.blocks {
match block {
Block::NormalBlock(block) => {
let ext_instructions =
ExtInstructions::from_instructions(&block.instructions)?.to_vec();
blocks.push(Block::NormalBlock(NormalBlock {
instructions: ext_instructions,
branch_block: block.branch_block.clone(),
default_block: block.default_block.clone(),
}));
}
Block::ExceptionBlock(block) => {
blocks.push(Block::ExceptionBlock(ExceptionBlock {
exception_handler: block.exception_handler.clone(),
default_block: block.default_block.clone(),
block_indexes: block.block_indexes.clone(),
}));
}
}
}
Ok(ControlFlowGraph::<ExtI> {
blocks,
start_index: simple_cfg.start_index.clone(),
})
}
#[cfg(test)]
mod test {
use crate::{
CodeObject,
cfg::{create_cfg, simple_cfg_to_ext_cfg},
v311::instructions::{Instruction, Instructions},
};
#[test]
fn simple_instructions() {
let instructions = Instructions::new(vec![
Instruction::LoadConst(0),
Instruction::LoadConst(1),
Instruction::CompareOp(0),
Instruction::PopJumpForwardIfTrue(2),
Instruction::LoadConst(2),
Instruction::ReturnValue(0),
Instruction::LoadConst(3),
Instruction::ReturnValue(0),
]);
let cfg = create_cfg(instructions.to_vec(), None).unwrap();
println!("{}", cfg.make_dot_graph());
insta::assert_debug_snapshot!(cfg);
}
#[test]
fn simple_program() {
// for x in range(10):
// if x == 9:
// print("yay")
// else:
// print("nay")
let program = crate::load_code(&b"\xe3\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x04\x00\x00\x00\x00\x00\x00\x00\xf3\\\x00\x00\x00\x97\x00\x02\x00e\x00d\x00\xa6\x01\x00\x00\xab\x01\x00\x00\x00\x00\x00\x00\x00\x00D\x00]\x1fZ\x01e\x01d\x01k\x02\x00\x00\x00\x00r\x0c\x02\x00e\x02d\x02\xa6\x01\x00\x00\xab\x01\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x8c\x14\x02\x00e\x02d\x03\xa6\x01\x00\x00\xab\x01\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x8c d\x04S\x00)\x05\xe9\n\x00\x00\x00\xe9\t\x00\x00\x00\xda\x03yay\xda\x03nayN)\x03\xda\x05range\xda\x01x\xda\x05print\xa9\x00\xf3\x00\x00\x00\x00z\x08<string>\xfa\x08<module>r\x0b\x00\x00\x00\x01\x00\x00\x00sM\x00\x00\x00\xf0\x03\x01\x01\x01\xe0\t\x0e\x88\x15\x88r\x89\x19\x8c\x19\xf0\x00\x04\x01\x15\xf0\x00\x04\x01\x15\x80A\xd8\x07\x08\x88A\x82v\x80v\xd8\x08\r\x88\x05\x88e\x89\x0c\x8c\x0c\x88\x0c\x88\x0c\xe0\x08\r\x88\x05\x88e\x89\x0c\x8c\x0c\x88\x0c\x88\x0c\xf0\t\x04\x01\x15\xf0\x00\x04\x01\x15r\n\x00\x00\x00"[..], (3, 11).into()).unwrap();
let instructions = match program {
CodeObject::V311(code) => code.code,
_ => unreachable!(),
};
dbg!(&instructions);
let cfg = create_cfg(instructions.to_vec(), None).unwrap();
dbg!(&cfg);
println!("{}", cfg.make_dot_graph());
insta::assert_debug_snapshot!(cfg);
}
#[test]
fn simple_to_ext_instructions() {
let instructions = Instructions::new(vec![
Instruction::ExtendedArg(1),
Instruction::LoadConst(0),
Instruction::LoadConst(1),
Instruction::CompareOp(0),
Instruction::PopJumpForwardIfTrue(2),
Instruction::LoadConst(2),
Instruction::ReturnValue(0),
Instruction::LoadConst(3),
Instruction::ReturnValue(0),
]);
let cfg = create_cfg(instructions.to_vec(), None).unwrap();
println!("{}", cfg.make_dot_graph());
let cfg = simple_cfg_to_ext_cfg(&cfg).unwrap();
println!("{}", cfg.make_dot_graph());
insta::assert_debug_snapshot!(cfg);
}
#[test]
fn simple_to_ext_instructions_ext_on_jump() {
let instructions = Instructions::new(vec![
Instruction::LoadConst(0),
Instruction::LoadConst(1),
Instruction::CompareOp(0),
Instruction::ExtendedArg(1),
Instruction::PopJumpForwardIfTrue(2), // Invalid jump target
Instruction::LoadConst(2),
Instruction::ReturnValue(0),
Instruction::LoadConst(3),
Instruction::ReturnValue(0),
]);
let cfg = create_cfg(instructions.to_vec(), None).unwrap();
println!("{}", cfg.make_dot_graph());
let cfg = simple_cfg_to_ext_cfg(&cfg).unwrap();
println!("{}", cfg.make_dot_graph());
insta::assert_debug_snapshot!(cfg);
}
#[test]
fn ext_trick() {
let instructions = Instructions::new(vec![
Instruction::LoadConst(0),
Instruction::PopJumpForwardIfTrue(1),
Instruction::ExtendedArg(0),
Instruction::ExtendedArg(0),
Instruction::StoreName(0),
]);
let cfg = create_cfg(instructions.to_vec(), None).unwrap();
println!("{}", cfg.make_dot_graph());
insta::assert_debug_snapshot!(cfg);
}
#[test]
fn ext_trick_with_jump() {
// TODO: This does not work yet
let instructions = Instructions::new(vec![
Instruction::LoadConst(0),
Instruction::PopJumpForwardIfTrue(1),
Instruction::ExtendedArg(0),
Instruction::ExtendedArg(0),
Instruction::JumpForward(0),
]);
let cfg = create_cfg(instructions.to_vec(), None).unwrap();
println!("{}", cfg.make_dot_graph());
insta::assert_debug_snapshot!(cfg);
}
#[test]
fn test_exception_block_311() {
// print("hi")
// try:
// print(1)
// except:
// print(2)
let program = crate::load_code(&b"\xe3\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x04\x00\x00\x00\x00\x00\x00\x00\xf3Z\x00\x00\x00\x97\x00\x02\x00e\x00d\x00\xa6\x01\x00\x00\xab\x01\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\t\x00\x02\x00e\x00d\x01\xa6\x01\x00\x00\xab\x01\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00d\x03S\x00#\x00\x01\x00\x02\x00e\x00d\x02\xa6\x01\x00\x00\xab\x01\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00Y\x00d\x03S\x00x\x03Y\x00w\x01)\x04\xda\x02hi\xe9\x01\x00\x00\x00\xe9\x02\x00\x00\x00N)\x01\xda\x05print\xa9\x00\xf3\x00\x00\x00\x00z\x08<string>\xfa\x08<module>r\x08\x00\x00\x00\x01\x00\x00\x00sD\x00\x00\x00\xf0\x03\x01\x01\x01\xe0\x00\x05\x80\x05\x80d\x81\x0b\x84\x0b\x80\x0b\xf0\x02\x03\x01\r\xd8\x04\t\x80E\x88!\x81H\x84H\x80H\x80H\x80H\xf8\xf0\x02\x01\x01\r\xd8\x04\t\x80E\x88!\x81H\x84H\x80H\x80H\x80H\x80H\xf8\xf8\xf8s\x08\x00\x00\x00\x8d\x0b\x1a\x00\x9a\r*\x03"[..], (3, 11).into()).unwrap();
let (instructions, exception_table) = match program {
CodeObject::V311(code) => (code.code.clone(), code.exception_table().unwrap()),
_ => unreachable!(),
};
let cfg = create_cfg(instructions.to_vec(), Some(exception_table)).unwrap();
let cfg = simple_cfg_to_ext_cfg(&cfg).unwrap();
println!("{}", cfg.make_dot_graph());
insta::assert_debug_snapshot!(cfg);
}
#[test]
fn test_async_func() {
// result = x()
// while not result:
// await self.wait()
// result = x()
// return result
let program = crate::load_code(&b"\xe3\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x02\x00\x00\x00\xc3\x00\x00\x00s(\x00\x00\x00\x81\x01t\x00\x83\x00}\x00|\x00s\x12t\x01\xa0\x02\xa1\x00I\x00d\x00H\x00\x01\x00t\x00\x83\x00}\x00|\x00r\x06|\x00S\x00)\x01N)\x03\xda\x01x\xda\x04self\xda\x04wait)\x01\xda\x06result\xa9\x00r\x05\x00\x00\x00\xfa\x07<stdin>\xda\x04test\x01\x00\x00\x00s\x0e\x00\x00\x00\x02\x80\x06\x01\x04\x01\x0e\x01\x06\x01\x04\xfe\x04\x03"[..], (3, 10).into()).unwrap();
let instructions = match program {
CodeObject::V310(code) => code.code.clone(),
_ => unreachable!(),
};
let cfg = create_cfg(instructions.to_vec(), None).unwrap();
let cfg = simple_cfg_to_ext_cfg(&cfg).unwrap();
println!("{}", cfg.make_dot_graph());
insta::assert_debug_snapshot!(cfg);
}
#[test]
fn test_311_async_exception() {
// This is the function `create_server` in the file `base_events.pyc` in the asyncio module
let program = crate::load_code(&b"\xe3\x04\x00\x00\x00\x00\x00\x00\x00\n\x00\x00\x00\n\x00\x00\x00\x83\x00\x00\x00\xf3H\x07\x00\x00\x87\x00\x87\x03\x87\x04\x87\x05K\x00\x01\x00\x97\x00t\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00|\x08t\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xa6\x02\x00\x00\xab\x02\x00\x00\x00\x00\x00\x00\x00\x00r\x0ft\x05\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00d\x01\xa6\x01\x00\x00\xab\x01\x00\x00\x00\x00\x00\x00\x00\x00\x82\x01|\x0b\x81\x11|\x08\x80\x0ft\x07\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00d\x03\xa6\x01\x00\x00\xab\x01\x00\x00\x00\x00\x00\x00\x00\x00\x82\x01|\x0c\x81\x11|\x08\x80\x0ft\x07\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00d\x04\xa6\x01\x00\x00\xab\x01\x00\x00\x00\x00\x00\x00\x00\x00\x82\x01|\x06\x81\x0ft\t\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00|\x06\xa6\x01\x00\x00\xab\x01\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00|\x02\x80\x03\x89\x03\x90\x02\x81y|\x06\x81\x0ft\x07\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00d\x05\xa6\x01\x00\x00\xab\x01\x00\x00\x00\x00\x00\x00\x00\x00\x82\x01|\t\x80 t\n\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00j\x06\x00\x00\x00\x00\x00\x00\x00\x00d\x06k\x02\x00\x00\x00\x00o\x0ft\x0e\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00j\x08\x00\x00\x00\x00\x00\x00\x00\x00d\x07k\x03\x00\x00\x00\x00}\tg\x00}\x0e|\x02d\x08k\x02\x00\x00\x00\x00r\x04d\x02g\x01}\x0fn:t\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00|\x02t\x12\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xa6\x02\x00\x00\xab\x02\x00\x00\x00\x00\x00\x00\x00\x00s\x1ft\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00|\x02t\x14\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00j\x0b\x00\x00\x00\x00\x00\x00\x00\x00j\x0c\x00\x00\x00\x00\x00\x00\x00\x00\xa6\x02\x00\x00\xab\x02\x00\x00\x00\x00\x00\x00\x00\x00s\x04|\x02g\x01}\x0fn\x02|\x02}\x0f\x88\x04\x88\x05\x88\x03\x88\x00f\x04d\t\x84\x08|\x0fD\x00\xa6\x00\x00\x00\xab\x00\x00\x00\x00\x00\x00\x00\x00\x00}\x10t\x1b\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00j\x0e\x00\x00\x00\x00\x00\x00\x00\x00|\x10\x8e\x00\x83\x00d\x02{\x03V\x00\x97\x03\x86\x04}\x11t\x1f\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00t \x00\x00\x00\x00\x00\x00\x00\x00\x00\x00j\x11\x00\x00\x00\x00\x00\x00\x00\x00\xa0\x12\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00|\x11\xa6\x01\x00\x00\xab\x01\x00\x00\x00\x00\x00\x00\x00\x00\xa6\x01\x00\x00\xab\x01\x00\x00\x00\x00\x00\x00\x00\x00}\x11d\n}\x12\t\x00|\x11D\x00\x90\x01]i}\x13|\x13\\\x05\x00\x00}\x14}\x15}\x16}\x17}\x18\t\x00t&\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xa0\x13\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00|\x14|\x15|\x16\xa6\x03\x00\x00\xab\x03\x00\x00\x00\x00\x00\x00\x00\x00}\x06n;#\x00t&\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00j\x14\x00\x00\x00\x00\x00\x00\x00\x00$\x00r)\x01\x00\x89\x00j\x15\x00\x00\x00\x00\x00\x00\x00\x00r\x1ft,\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xa0\x17\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00d\x0b|\x14|\x15|\x16d\x0c\xac\r\xa6\x05\x00\x00\xab\x05\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00Y\x00\x8c`w\x00x\x03Y\x00w\x01|\x0e\xa0\x18\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00|\x06\xa6\x01\x00\x00\xab\x01\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00|\tr+|\x06\xa0\x19\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00t&\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00j\x1a\x00\x00\x00\x00\x00\x00\x00\x00t&\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00j\x1b\x00\x00\x00\x00\x00\x00\x00\x00d\x0c\xa6\x03\x00\x00\xab\x03\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00|\nr\x0ft9\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00|\x06\xa6\x01\x00\x00\xab\x01\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00t:\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00rP|\x14t&\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00j\x1e\x00\x00\x00\x00\x00\x00\x00\x00k\x02\x00\x00\x00\x00r@t?\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00t&\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00d\x0e\xa6\x02\x00\x00\xab\x02\x00\x00\x00\x00\x00\x00\x00\x00r+|\x06\xa0\x19\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00t&\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00j \x00\x00\x00\x00\x00\x00\x00\x00t&\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00j!\x00\x00\x00\x00\x00\x00\x00\x00d\x0c\xa6\x03\x00\x00\xab\x03\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\t\x00|\x06\xa0\"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00|\x18\xa6\x01\x00\x00\xab\x01\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x90\x01\x8c&#\x00tF\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00$\x00r8}\x19tG\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00|\x19j$\x00\x00\x00\x00\x00\x00\x00\x00d\x0f|\x18\x9b\x02d\x10|\x19j%\x00\x00\x00\x00\x00\x00\x00\x00\xa0&\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xa6\x00\x00\x00\xab\x00\x00\x00\x00\x00\x00\x00\x00\x00\x9b\x01\x9d\x04\xa6\x02\x00\x00\xab\x02\x00\x00\x00\x00\x00\x00\x00\x00d\x02\x82\x02d\x02}\x19~\x19w\x01w\x00x\x03Y\x00w\x01d\x0c}\x12|\x12s\x19|\x0eD\x00]\x16}\x06|\x06\xa0\'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xa6\x00\x00\x00\xab\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x8c\x17n\\#\x00|\x12s\x19|\x0eD\x00]\x17}\x06|\x06\xa0\'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xa6\x00\x00\x00\xab\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x8c\x17w\x00w\x00x\x03Y\x00w\x01|\x06\x80\x0ft\x07\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00d\x11\xa6\x01\x00\x00\xab\x01\x00\x00\x00\x00\x00\x00\x00\x00\x82\x01|\x06j(\x00\x00\x00\x00\x00\x00\x00\x00t&\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00j)\x00\x00\x00\x00\x00\x00\x00\x00k\x03\x00\x00\x00\x00r\x12t\x07\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00d\x12|\x06\x9b\x02\x9d\x02\xa6\x01\x00\x00\xab\x01\x00\x00\x00\x00\x00\x00\x00\x00\x82\x01|\x06g\x01}\x0e|\x0eD\x00]\x17}\x06|\x06\xa0*\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00d\n\xa6\x01\x00\x00\xab\x01\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x8c\x18tW\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x89\x00|\x0e|\x01|\x08|\x07|\x0b|\x0c\xa6\x07\x00\x00\xab\x07\x00\x00\x00\x00\x00\x00\x00\x00}\x1a|\rr4|\x1a\xa0,\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xa6\x00\x00\x00\xab\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00t\x1a\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xa0-\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00d\x13\xa6\x01\x00\x00\xab\x01\x00\x00\x00\x00\x00\x00\x00\x00\x83\x00d\x02{\x03V\x00\x97\x03\x86\x04\x01\x00\x89\x00j\x15\x00\x00\x00\x00\x00\x00\x00\x00r\x1bt,\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xa0.\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00d\x14|\x1a\xa6\x02\x00\x00\xab\x02\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00|\x1aS\x00)\x15\xe1\x05\x02\x00\x00Create a TCP server.\n The host parameter can be a string, in that case the TCP server is\n bound to host and port.\n The host parameter can also be a sequence of strings and in that case\n the TCP server is bound to all hosts of the sequence. If a host\n appears multiple times (possibly indirectly e.g. when hostnames\n resolve to the same IP address), the server is only bound once to that\n host.\n Return a Server object which can be used to stop the service.\n This method is a coroutine.\n z*ssl argument must be an SSLContext or NoneNz1ssl_handshake_timeout is only meaningful with sslz0ssl_shutdown_timeout is only meaningful with sslz8host/port and sock can not be specified at the same time\xda\x05posix\xda\x06cygwin\xda\x00c\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x08\x00\x00\x00\x13\x00\x00\x00\xf3B\x00\x00\x00\x95\x04\x97\x00g\x00|\x00]\x1b}\x01\x89\x05\xa0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00|\x01\x89\x04\x89\x02\x89\x03\xac\x00\xa6\x04\x00\x00\xab\x04\x00\x00\x00\x00\x00\x00\x00\x00\x91\x02\x8c\x1cS\x00)\x01)\x02\xda\x06family\xda\x05flags)\x01\xda\x1a_create_server_getaddrinfo)\x06\xda\x02.0\xda\x04hostr\x07\x00\x00\x00r\x08\x00\x00\x00\xda\x04port\xda\x04selfs\x06\x00\x00\x00 \x80\x80\x80\x80\xfa\x07<stdin>\xfa\n<listcomp>z!create_server.<locals>.<listcomp>1\x00\x00\x00sG\x00\x00\x00\xf8\x80\x00\xf0\x00\x02\x0e!\xf0\x00\x02\x0e!\xf0\x00\x02\x0e!\xe0\x12\x16\xf0\x05\x00\x0f\x13\xd7\x0e-\xd2\x0e-\xa8d\xb0D\xc0\x16\xd849\xf0\x03\x00\x0f.\xf1\x00\x01\x0f;\xf4\x00\x01\x0f;\xf0\x00\x02\x0e!\xf0\x00\x02\x0e!\xf0\x00\x02\x0e!\xf3\x00\x00\x00\x00Fz:create_server() failed to create socket.socket(%r, %r, %r)T)\x01\xda\x08exc_info\xda\x0cIPPROTO_IPV6z*error while attempting to bind on address z\x02: z)Neither host/port nor sock were specifiedz\"A Stream Socket was expected, got \xe9\x00\x00\x00\x00z\r%r is serving)/\xda\nisinstance\xda\x04bool\xda\tTypeError\xda\nValueError\xda\x11_check_ssl_socket\xda\x02os\xda\x04name\xda\x03sys\xda\x08platform\xda\x03str\xda\x0bcollections\xda\x03abc\xda\x08Iterable\xda\x05tasks\xda\x06gather\xda\x03set\xda\titertools\xda\x05chain\xda\rfrom_iterable\xda\x06socket\xda\x05error\xda\x06_debug\xda\x06logger\xda\x07warning\xda\x06append\xda\nsetsockopt\xda\nSOL_SOCKET\xda\x0cSO_REUSEADDR\xda\x0e_set_reuseport\xda\t_HAS_IPv6\xda\x08AF_INET6\xda\x07hasattrr\x12\x00\x00\x00\xda\x0bIPV6_V6ONLY\xda\x04bind\xda\x07OSError\xda\x05errno\xda\x08strerror\xda\x05lower\xda\x05close\xda\x04type\xda\x0bSOCK_STREAM\xda\x0bsetblocking\xda\x06Server\xda\x0e_start_serving\xda\x05sleep\xda\x04info)\x1br\r\x00\x00\x00\xda\x10protocol_factoryr\x0b\x00\x00\x00r\x0c\x00\x00\x00r\x07\x00\x00\x00r\x08\x00\x00\x00\xda\x04sock\xda\x07backlog\xda\x03ssl\xda\rreuse_address\xda\nreuse_port\xda\x15ssl_handshake_timeout\xda\x14ssl_shutdown_timeout\xda\rstart_serving\xda\x07sockets\xda\x05hosts\xda\x02fs\xda\x05infos\xda\tcompleted\xda\x03res\xda\x02af\xda\x08socktype\xda\x05proto\xda\tcanonname\xda\x02sa\xda\x03err\xda\x06servers\x1b\x00\x00\x00` ``` r\x0e\x00\x00\x00\xda\rcreate_serverrX\x00\x00\x00\x01\x00\x00\x00s\xa1\x04\x00\x00\xf8\xf8\xf8\xf8\xe8\x00\xe8\x00\x80\x00\xf50\x00\x08\x12\x90#\x95t\xd1\x07\x1c\xd4\x07\x1c\xf0\x00\x01\x05F\x01\xdd\x0e\x17\xd0\x18D\xd1\x0eE\xd4\x0eE\xd0\x08E\xd8\x07\x1c\xd0\x07(\xa8S\xa8[\xdd\x0e\x18\xd8\x0c?\xf1\x03\x01\x0fA\x01\xf4\x00\x01\x0fA\x01\xf0\x00\x01\tA\x01\xe0\x07\x1b\xd0\x07\'\xa8C\xa8K\xdd\x0e\x18\xd8\x0c>\xf1\x03\x01\x0f@\x01\xf4\x00\x01\x0f@\x01\xf0\x00\x01\t@\x01\xe0\x07\x0b\xd0\x07\x17\xdd\x08\x19\x98$\xd1\x08\x1f\xd4\x08\x1f\xd0\x08\x1f\xd8\x07\x0b\xd0\x07\x17\x984\xd1\x1b+\xd8\x0b\x0f\xd0\x0b\x1b\xdd\x12\x1c\xd8\x10J\xf1\x03\x01\x13L\x01\xf4\x00\x01\x13L\x01\xf0\x00\x01\rL\x01\xe0\x0b\x18\xd0\x0b \xdd\x1c\x1e\x9cG\xa0w\xd2\x1c.\xd0\x1cK\xb53\xb4<\xc08\xd23K\x88M\xd8\x12\x14\x88\x07\xd8\x0b\x0f\x902\x8a:\x88:\xd8\x15\x19\x90F\x88E\x88E\xdd\x0e\x18\x98\x14\x9ds\xd1\x0e#\xd4\x0e#\xf0\x00\x04\t\x19\xdd\x12\x1c\x98T\xa5;\xa4?\xd4#;\xd1\x12<\xd4\x12<\xf0\x03\x04\t\x19\xe0\x15\x19\x90F\x88E\x88E\xe0\x14\x18\x88E\xf0\x02\x02\x0e!\xf0\x00\x02\x0e!\xf0\x00\x02\x0e!\xf0\x00\x02\x0e!\xf0\x00\x02\x0e!\xf0\x00\x02\x0e!\xf0\x00\x02\x0e!\xe0\x1a\x1f\xf0\x05\x02\x0e!\xf1\x00\x02\x0e!\xf4\x00\x02\x0e!\x88\x02\xf5\x06\x00\x17\x1c\x94l\xa0B\xd0\x16\'\xd0\x10\'\xd0\x10\'\xd0\x10\'\xd0\x10\'\xd0\x10\'\xd0\x10\'\x88\x05\xdd\x10\x13\x95I\x94O\xd7\x141\xd2\x141\xb0%\xd1\x148\xd4\x148\xd1\x109\xd4\x109\x88\x05\xd8\x14\x19\x88\t\xf0\x02%\t!\xd8\x17\x1c\xf0\x00\x1f\rJ\x01\xf1\x00\x1f\rJ\x01\x90\x03\xd858\xd1\x102\x90\x02\x90H\x98e\xa0Y\xb0\x02\xf0\x02\x08\x11\x1d\xdd\x1b!\x9f=\x9a=\xa8\x12\xa8X\xb0u\xd1\x1b=\xd4\x1b=\x90D\x90D\xf8\xdd\x17\x1d\x94|\xf0\x00\x06\x11\x1d\xf0\x00\x06\x11\x1d\xf0\x00\x06\x11\x1d\xe0\x17\x1b\x94{\xf0\x00\x03\x15K\x01\xdd\x18\x1e\x9f\x0e\x9a\x0e\xf0\x00\x01(C\x01\xe0\')\xa88\xb0U\xc0T\xf0\x05\x00\x19\'\xf1\x00\x02\x19K\x01\xf4\x00\x02\x19K\x01\xf0\x00\x02\x19K\x01\xf0\x06\x00\x15\x1d\x90H\xf0\r\x06\x11\x1d\xf8\xf8\xf8\xf0\x0e\x00\x11\x18\x97\x0e\x92\x0e\x98t\xd1\x10$\xd4\x10$\xd0\x10$\xd8\x13 \xf0\x00\x02\x11F\x01\xd8\x14\x18\x97O\x92O\xdd\x18\x1e\xd4\x18)\xad6\xd4+>\xc0\x04\xf1\x03\x01\x15F\x01\xf4\x00\x01\x15F\x01\xf0\x00\x01\x15F\x01\xe0\x13\x1d\xf0\x00\x01\x11)\xdd\x14\"\xa04\xd1\x14(\xd4\x14(\xd0\x14(\xf5\x08\x00\x15\x1e\xf0\x00\x05\x11*\xd8\x18\x1a\x9df\x9co\xd2\x18-\xd0\x18-\xdd\x18\x1f\xa5\x06\xa8\x0e\xd1\x187\xd4\x187\xf0\x03\x00\x19.\xe0\x14\x18\x97O\x92O\xa5F\xd4$7\xdd$*\xd4$6\xd8$(\xf1\x05\x02\x15*\xf4\x00\x02\x15*\xf0\x00\x02\x15*\xf0\x06\x05\x11J\x01\xd8\x14\x18\x97I\x92I\x98b\x91M\x94M\x90M\x91M\xf8\xdd\x17\x1e\xf0\x00\x03\x11J\x01\xf0\x00\x03\x11J\x01\xf0\x00\x03\x11J\x01\xdd\x1a!\xa0#\xa4)\xa0)\xe0%\'\xa0R\xa0R\xa8\x13\xac\x1c\xd7);\xd2);\xd1)=\xd4)=\xd0)=\xf0\x05\x02.?\xf1\x00\x02\x1b@\x01\xf4\x00\x02\x1b@\x01\xe0EI\xf0\x05\x02\x15J\x01\xf8\xf8\xf8\xf8\xf0\x03\x03\x11J\x01\xf8\xf8\xf8\xf0\x08\x00\x19\x1d\x88I\xe0\x13\x1c\xf0\x00\x02\r!\xd8\x1c#\xf0\x00\x01\x11!\xf0\x00\x01\x11!\x90D\xd8\x14\x18\x97J\x92J\x91L\x94L\x90L\x90L\xf8\xf8\xf0\x05\x00\x14\x1d\xf0\x00\x02\r!\xd8\x1c#\xf0\x00\x01\x11!\xf0\x00\x01\x11!\x90D\xd8\x14\x18\x97J\x92J\x91L\x94L\x90L\x90L\xf0\x05\x02\r!\xf0\x02\x01\x11!\xf8\xf8\xf8\xf0\x06\x00\x0c\x10\x88<\xdd\x12\x1c\xd0\x1dH\xd1\x12I\xd4\x12I\xd0\x0cI\xd8\x0b\x0f\x8c9\x9d\x06\xd4\x18*\xd2\x0b*\xd0\x0b*\xdd\x12\x1c\xd0\x1dJ\xc0$\xd0\x1dJ\xd0\x1dJ\xd1\x12K\xd4\x12K\xd0\x0cK\xd8\x13\x17\x90&\x88\x07\xd8\x10\x17\xf0\x00\x01\x05 \xf0\x00\x01\x05 \x88\x04\xd8\x08\x0c\xd7\x08\x18\xd2\x08\x18\x98\x15\xd1\x08\x1f\xd4\x08\x1f\xd0\x08\x1f\xd0\x08\x1f\xdd\r\x13\x90D\x98\'\xd0#3\xd8\x14\x17\x98\x17\xd0\"7\xd8\x14(\xf1\x05\x02\x0e*\xf4\x00\x02\x0e*\x80F\xf0\x06\x00\x08\x15\xf0\x00\x04\x05\x1d\xd8\x08\x0e\xd7\x08\x1d\xd2\x08\x1d\xd1\x08\x1f\xd4\x08\x1f\xd0\x08\x1f\xf5\x06\x00\x0f\x14\x8fk\x8ak\x98!\x89n\x8cn\xd0\x08\x1c\xd0\x08\x1c\xd0\x08\x1c\xd0\x08\x1c\xd0\x08\x1c\xd0\x08\x1c\xd0\x08\x1c\xd8\x07\x0b\x84{\xf0\x00\x01\x05-\xdd\x08\x0e\x8f\x0b\x8a\x0b\x90O\xa0V\xd1\x08,\xd4\x08,\xd0\x08,\xd8\x0b\x11\x80MsU\x00\x00\x00\xc44\rJ?\x00\xc5\x02\x1cE\x1f\x02\xc5\x1e\x01J?\x00\xc5\x1f5F\x17\x05\xc6\x14\x02J?\x00\xc6\x16\x01F\x17\x05\xc6\x17B-J?\x00\xc9\x05\x15I\x1c\x02\xc9\x1a\x02J?\x00\xc9\x1c\nJ\x1e\x05\xc9&3J\x19\x05\xca\x19\x05J\x1e\x05\xca\x1e\x05J?\x00\xca?\x1eK\x1d\x03"[..], (3, 11).into()).unwrap();
let (instructions, exception_table) = match program {
CodeObject::V311(code) => (code.code.clone(), code.exception_table().unwrap()),
_ => unreachable!(),
};
let cfg = create_cfg(instructions.to_vec(), Some(exception_table)).unwrap();
let cfg = simple_cfg_to_ext_cfg(&cfg).unwrap();
println!("{}", cfg.make_dot_graph());
insta::with_settings!({
filters => vec![(r"block_indexes: \[[\d\s,]*\]", "[block_indexes filtered because non-deterministic order]")]
}, {
insta::assert_debug_snapshot!(cfg);
});
}
#[test]
fn test_311_exception_handling_grouped() {
// This is the function `runcode` in the file `pyshell.pyc` in the idlelib module
let program = crate::load_code(&b"\xe3\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x07\x00\x00\x00\x03\x00\x00\x00\xf32\x04\x00\x00\x97\x00|\x00j\x00\x00\x00\x00\x00\x00\x00\x00\x00j\x01\x00\x00\x00\x00\x00\x00\x00\x00r\x14|\x00\xa0\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xa6\x00\x00\x00\xab\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00|\x00\xa0\x03\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xa6\x00\x00\x00\xab\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00|\x00j\x04\x00\x00\x00\x00\x00\x00\x00\x00}\x02\t\x00|\x00j\x00\x00\x00\x00\x00\x00\x00\x00\x00\xa0\x05\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xa6\x00\x00\x00\xab\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00|\x02s+|\x00j\x06\x00\x00\x00\x00\x00\x00\x00\x00\x81$|\x00j\x06\x00\x00\x00\x00\x00\x00\x00\x00\xa0\x07\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00d\x02d\x03|\x01f\x01i\x00\xa6\x04\x00\x00\xab\x04\x00\x00\x00\x00\x00\x00\x00\x00|\x00_\x08\x00\x00\x00\x00\x00\x00\x00\x00n3|\x02r\x1c|\x02\xa0\t\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00|\x01|\x00j\n\x00\x00\x00\x00\x00\x00\x00\x00\xa6\x02\x00\x00\xab\x02\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00n\x15t\x17\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00|\x01|\x00j\n\x00\x00\x00\x00\x00\x00\x00\x00\xa6\x02\x00\x00\xab\x02\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00n\xf5#\x00t\x18\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00$\x00rN\x01\x00|\x00j\x00\x00\x00\x00\x00\x00\x00\x00\x00j\r\x00\x00\x00\x00\x00\x00\x00\x00s>t\x1c\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xa0\x0f\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00d\x04d\x05d\x06|\x00j\x00\x00\x00\x00\x00\x00\x00\x00\x00j\x10\x00\x00\x00\x00\x00\x00\x00\x00\xac\x07\xa6\x04\x00\x00\xab\x04\x00\x00\x00\x00\x00\x00\x00\x00r\x01\x82\x00|\x00\xa0\x11\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xa6\x00\x00\x00\xab\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00n\x01\x82\x00Y\x00n\x9e\x01\x00t$\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00rIt\'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00d\x08|\x00j\x00\x00\x00\x00\x00\x00\x00\x00\x00j\x14\x00\x00\x00\x00\x00\x00\x00\x00\xac\t\xa6\x02\x00\x00\xab\x02\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00|\x00\xa0\x11\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xa6\x00\x00\x00\xab\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00|\x00j\x00\x00\x00\x00\x00\x00\x00\x00\x00\xa0\x15\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xa6\x00\x00\x00\xab\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00nH|\x00j\x00\x00\x00\x00\x00\x00\x00\x00\x00j\x16\x00\x00\x00\x00\x00\x00\x00\x00r(d\n|\x00j\x00\x00\x00\x00\x00\x00\x00\x00\x00_\x16\x00\x00\x00\x00\x00\x00\x00\x00t\'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00d\x0b|\x00j\x00\x00\x00\x00\x00\x00\x00\x00\x00j\x14\x00\x00\x00\x00\x00\x00\x00\x00\xac\t\xa6\x02\x00\x00\xab\x02\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00n\x14|\x00\xa0\x11\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xa6\x00\x00\x00\xab\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00Y\x00n\x03x\x03Y\x00w\x01t$\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00s-\t\x00|\x00j\x00\x00\x00\x00\x00\x00\x00\x00\x00\xa0\x15\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xa6\x00\x00\x00\xab\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00d\x01S\x00#\x00t.\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00$\x00r\x04\x01\x00Y\x00d\x01S\x00w\x00x\x03Y\x00w\x01d\x01S\x00#\x00t$\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00s+\t\x00|\x00j\x00\x00\x00\x00\x00\x00\x00\x00\x00\xa0\x15\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xa6\x00\x00\x00\xab\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00w\x00#\x00t.\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00$\x00r\x03\x01\x00Y\x00w\x00w\x00x\x03Y\x00w\x01w\x00x\x03Y\x00w\x01)\x0c\xfa\x1aOverride base class methodN\xda\x04exec\xda\x07runcodez\x05Exit?z\x1fDo you want to exit altogether?\xda\x03yes)\x02\xda\x07default\xda\x06parentz IDLE internal error in runcode())\x01\xda\x04fileF\xda\x11KeyboardInterrupt)\x18\xda\ttkconsole\xda\texecuting\xda\x12restart_subprocess\xda\x0echecklinecache\xda\x08debugger\xda\x0ebeginexecuting\xda\x06rpcclt\xda\nasyncqueue\xda\nactive_seq\xda\x03run\xda\x06localsr\x03\x00\x00\x00\xda\nSystemExit\xda\x07closing\xda\nmessagebox\xda\x08askyesno\xda\x04text\xda\rshowtraceback\xda\x0euse_subprocess\xda\x05print\xda\x06stderr\xda\x0cendexecuting\xda\x08canceled\xda\x0eAttributeError)\x03\xda\x04self\xda\x04coder\x0e\x00\x00\x00s\x03\x00\x00\x00 \xfa\x07<stdin>r\x04\x00\x00\x00r\x04\x00\x00\x00\x01\x00\x00\x00sr\x02\x00\x00\x80\x00\xe0\x07\x0b\x84~\xd4\x07\x1f\xf0\x00\x01\x05\"\xd8\x08\x0c\xd7\x08\x1f\xd2\x08\x1f\xd1\x08!\xd4\x08!\xd0\x08!\xd8\x04\x08\xd7\x04\x17\xd2\x04\x17\xd1\x04\x19\xd4\x04\x19\xd0\x04\x19\xd8\x0f\x13\x8c}\x80H\xf0\x02&\x05\x15\xd8\x08\x0c\x8c\x0e\xd7\x08%\xd2\x08%\xd1\x08\'\xd4\x08\'\xd0\x08\'\xd8\x0f\x17\xf0\x00\x06\t$\x98D\x9cK\xd0\x1c3\xd8\x1e\"\x9ck\xd7\x1e4\xd2\x1e4\xb0V\xb8Y\xd859\xb0G\xb8R\xf1\x03\x01\x1fA\x01\xf4\x00\x01\x1fA\x01\x88D\x8cO\x88O\xe0\r\x15\xf0\x00\x03\t$\xd8\x0c\x14\x8fL\x8aL\x98\x14\x98t\x9c{\xd1\x0c+\xd4\x0c+\xd0\x0c+\xd0\x0c+\xe5\x0c\x10\x90\x14\x90t\x94{\xd1\x0c#\xd4\x0c#\xd0\x0c#\xf8\xf8\xdd\x0b\x15\xf0\x00\x0b\x05\x12\xf0\x00\x0b\x05\x12\xf0\x00\x0b\x05\x12\xd8\x0f\x13\x8c~\xd4\x0f%\xf0\x00\n\t\x12\xdd\x0f\x19\xd7\x0f\"\xd2\x0f\"\xd8\x10\x17\xd8\x101\xd8\x18\x1d\xd8\x17\x1b\x94~\xd4\x17*\xf0\t\x00\x10#\xf1\x00\x04\x10,\xf4\x00\x04\x10,\xf0\x00\x07\r%\xf0\n\x00\x11\x16\xe0\x10\x14\xd7\x10\"\xd2\x10\"\xd1\x10$\xd4\x10$\xd0\x10$\xd0\x10$\xe0\x0c\x11\xf0\x05\x00\x11%\xd0\x10$\xf0\x06\x0b\x05%\xdd\x0b\x19\xf0\x00\n\t%\xdd\x0c\x11\xd0\x124\xd8\x17\x1b\x94~\xd4\x17,\xf0\x03\x01\r.\xf1\x00\x01\r.\xf4\x00\x01\r.\xf0\x00\x01\r.\xe0\x0c\x10\xd7\x0c\x1e\xd2\x0c\x1e\xd1\x0c \xd4\x0c \xd0\x0c \xd8\x0c\x10\x8cN\xd7\x0c\'\xd2\x0c\'\xd1\x0c)\xd4\x0c)\xd0\x0c)\xd0\x0c)\xe0\x0f\x13\x8c~\xd4\x0f&\xf0\x00\x04\r%\xd8*/\x90\x04\x94\x0e\xd4\x10\'\xdd\x10\x15\xd0\x16)\xb0\x04\xb4\x0e\xd40E\xd0\x10F\xd1\x10F\xd4\x10F\xd0\x10F\xd0\x10F\xe0\x10\x14\xd7\x10\"\xd2\x10\"\xd1\x10$\xd4\x10$\xd0\x10$\xf8\xf8\xf8\xf8\xf8\xe5\x0f\x1d\xf0\x00\x04\t\x15\xf0\x02\x03\r\x15\xd8\x10\x14\x94\x0e\xd7\x10+\xd2\x10+\xd1\x10-\xd4\x10-\xd0\x10-\xd0\x10-\xd0\x10-\xf8\xdd\x13!\xf0\x00\x01\r\x15\xf0\x00\x01\r\x15\xf0\x00\x01\r\x15\xd8\x10\x14\x90\x04\x90\x04\xf0\x03\x01\r\x15\xf8\xf8\xf8\xf0\x07\x04\t\x15\xf0\x00\x04\t\x15\xf8\x8d~\xf0\x00\x04\t\x15\xf0\x02\x03\r\x15\xd8\x10\x14\x94\x0e\xd7\x10+\xd2\x10+\xd1\x10-\xd4\x10-\xd0\x10-\xd0\x10-\xf8\xdd\x13!\xf0\x00\x01\r\x15\xf0\x00\x01\r\x15\xf0\x00\x01\r\x15\xd8\x10\x14\x90\x04\xf0\x03\x01\r\x15\xf8\xf8\xf8\xf0\x07\x04\t\x15\xf8\xf8\xf8sb\x00\x00\x00\xbdA9B7\x00\xc26\x01G\"\x00\xc27A\x15F)\x03\xc4\x0c\x02G\"\x00\xc4\x0eB\x19F)\x03\xc6\'\x05G\"\x00\xc64\x19G\x0f\x00\xc7\x0f\nG\x1d\x03\xc7\x1c\x01G\x1d\x03\xc7\"\x08H\x16\x03\xc7+\x19H\x05\x04\xc8\x04\x01H\x16\x03\xc8\x05\nH\x12\x07\xc8\x0f\x02H\x16\x03\xc8\x11\x01H\x12\x07\xc8\x12\x04H\x16\x03"[..], (3, 11).into()).unwrap();
let (instructions, exception_table) = match program {
CodeObject::V311(code) => (code.code.clone(), code.exception_table().unwrap()),
_ => unreachable!(),
};
let cfg = create_cfg(instructions.to_vec(), Some(exception_table)).unwrap();
let cfg = simple_cfg_to_ext_cfg(&cfg).unwrap();
println!("{}", cfg.make_dot_graph());
insta::with_settings!({
filters => vec![(r"block_indexes: \[[\d\s,]*\]", "[block_indexes filtered because non-deterministic order]")]
}, {
insta::assert_debug_snapshot!(cfg);
});
}
#[test]
fn test_311_exception_handling_in_loop() {
// This is the function `testContinueStmt` in the file `py3_test_grammar.pyc` in the lib2to3 module
// It's an example of where the cfg will have less instructions than the original instruction list
// There is deadcode that can't be reached and will be removed by our cfg construction
let program = crate::load_code(&b"\xe3\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x04\x00\x00\x00\x03\x00\x00\x00\xf3\xce\x00\x00\x00\x97\x00d\x01}\x01|\x01r\x03d\x02}\x01\x8c\x05d\x03}\x02|\x02s\x0fd\x04}\x02\t\x00\x8c\x06#\x00\x01\x00d\x06}\x02Y\x00n\x03x\x03Y\x00w\x01|\x02\xaf\x0f|\x02d\x04k\x03\x00\x00\x00\x00r\x15|\x00\xa0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00|\x02\xa6\x01\x00\x00\xab\x01\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00d\x03}\x02|\x02s\x0ed\x07}\x02\t\x00\t\x00d\x04}\x02\x8c\t#\x00d\x04}\x02w\x00x\x03Y\x00w\x01|\x02d\x04k\x03\x00\x00\x00\x00r\x17|\x00\xa0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00|\x02\xa6\x01\x00\x00\xab\x01\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00d\x00S\x00d\x00S\x00)\x08N\xe9\x01\x00\x00\x00\xe9\x00\x00\x00\x00\xda\x00\xda\x02okz&continue failed to continue inside tryz'continue inside try called except blockz\x18finally block not called)\x01\xda\x04fail)\x03\xda\x04self\xda\x01i\xda\x03msgs\x03\x00\x00\x00 \xfa\x07<stdin>\xda\x10testContinueStmtr\x0b\x00\x00\x00\x01\x00\x00\x00s\xb4\x00\x00\x00\x80\x00\xe0\x08\t\x80A\xd8\n\x0b\xd0\x04\x1c\x90\x11\x88Q\x90H\xd8\n\x0c\x80C\xd8\x0e\x11\xf0\x00\x06\x05<\xd8\x0e\x12\x88\x03\xf0\x02\x04\t<\xd8\x0c\x14\xf8\xf0\x04\x01\t<\xd8\x12;\x88C\x88C\x88C\xf8\xf8\xf8\xf0\r\x00\x0f\x12\xf0\x00\x06\x05<\xf0\x0e\x00\x08\x0b\x88d\x82{\x80{\xd8\x08\x0c\x8f\t\x8a\t\x90#\x89\x0e\x8c\x0e\x88\x0e\xd8\n\x0c\x80C\xd8\x0e\x11\xf0\x00\x05\x05\x17\xd8\x0e(\x88\x03\xf0\x02\x03\t\x17\xd8\x0c\x14\xe0\x12\x16\x88C\x88C\xf8\x90$\x88C\x88J\x88J\x88J\x88J\xd8\x07\n\x88d\x82{\x80{\xd8\x08\x0c\x8f\t\x8a\t\x90#\x89\x0e\x8c\x0e\x88\x0e\x88\x0e\x88\x0e\xf0\x03\x00\x08\x13\x80{s\n\x00\x00\x00\x90\x04\x16\x03\xc1\x01\x04A\x05\x03"[..], (3, 11).into()).unwrap();
let (instructions, exception_table) = match program {
CodeObject::V311(code) => (code.code.clone(), code.exception_table().unwrap()),
_ => unreachable!(),
};
let cfg = create_cfg(instructions.to_vec(), Some(exception_table)).unwrap();
let cfg = simple_cfg_to_ext_cfg(&cfg).unwrap();
println!("{}", cfg.make_dot_graph());
insta::assert_debug_snapshot!(cfg);
}
}