From 71af84090e70e860c182d7295a1ad0f28ec2ce51 Mon Sep 17 00:00:00 2001 From: br0kej Date: Tue, 20 Aug 2024 10:41:35 +0100 Subject: [PATCH 1/5] adding display and tostring trait for feature type to enable saving specific paths based on feature type chosen --- src/agfj.rs | 2 +- src/bb.rs | 19 +++++++++++++++++++ 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/src/agfj.rs b/src/agfj.rs index 3bcf843..c5490a0 100644 --- a/src/agfj.rs +++ b/src/agfj.rs @@ -340,7 +340,7 @@ impl AGFJFunc { feature_type: FeatureType, architecture: &String, ) { - let full_output_path = get_save_file_path(path, output_path, None, None, None); + let full_output_path = get_save_file_path(path, output_path, None, Some(feature_type.to_string()), None); check_or_create_dir(&full_output_path); let file_name = path.file_name().unwrap(); let binding = file_name.to_string_lossy().to_string(); diff --git a/src/bb.rs b/src/bb.rs index 24bf818..0629646 100644 --- a/src/bb.rs +++ b/src/bb.rs @@ -7,6 +7,7 @@ use serde_aux::prelude::*; use serde_json::Value; use serde_with::{serde_as, DefaultOnError}; use std::collections::HashMap; +use std::fmt; use std::string::String; #[cfg(feature = "inference")] use std::sync::Arc; @@ -27,6 +28,24 @@ pub enum FeatureType { Pcode, } +impl fmt::Display for FeatureType { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let feature_type_str = match self { + FeatureType::Gemini => "gemini", + FeatureType::DiscovRE => "discovre", + FeatureType::DGIS => "dgis", + FeatureType::Tiknib => "tiknib", + FeatureType::Disasm => "disasm", + FeatureType::Esil => "esil", + FeatureType::ModelEmbedded => "embedded", + FeatureType::Encoded => "encoded", + FeatureType::Invalid => "invalid", + FeatureType::Pcode => "pcode", + }; + write!(f, "{}", feature_type_str) + } +} + #[derive(Serialize, Deserialize, Debug, PartialEq, Clone, Copy)] pub enum InstructionMode { ESIL, From b5f11e6bb0a48196b9e838ad43e64899bd3de542 Mon Sep 17 00:00:00 2001 From: br0kej Date: Tue, 20 Aug 2024 10:42:38 +0100 Subject: [PATCH 2/5] Adding asm.pseudo code option on by default for extract commands --- src/extract.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/extract.rs b/src/extract.rs index edfad28..3b4a9ef 100644 --- a/src/extract.rs +++ b/src/extract.rs @@ -897,13 +897,13 @@ impl FileToBeProcessed { debug!("Creating r2 handle with debugging"); R2PipeSpawnOptions { exepath: "radare2".to_owned(), - args: vec!["-e bin.cache=true", "-e log.level=0"], + args: vec!["-e bin.cache=true", "-e log.level=0", "-e asm.pseudo=true"], } } else { debug!("Creating r2 handle without debugging"); R2PipeSpawnOptions { exepath: "radare2".to_owned(), - args: vec!["-e bin.cache=true", "-e log.level=1", "-2"], + args: vec!["-e bin.cache=true", "-e log.level=1", "-2", "-e asm.pseudo=true"], } }; From a65f5f82cfa3cb8cb8f48585a3f064c259cdf5dd Mon Sep 17 00:00:00 2001 From: br0kej Date: Tue, 20 Aug 2024 10:44:32 +0100 Subject: [PATCH 3/5] Refactoring out the use of ins.opcode to instead use ins.disasm to ensure data generation does not use the pseduo code --- src/bb.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/bb.rs b/src/bb.rs index 0629646..ba3c4a7 100644 --- a/src/bb.rs +++ b/src/bb.rs @@ -281,7 +281,7 @@ impl ACFJBlock { for ins in self.ops.iter() { if ins.r#type != "invalid" { let opcode = ins - .opcode + .disasm .as_ref() .unwrap() .split_whitespace() @@ -346,7 +346,7 @@ impl ACFJBlock { for ins in self.ops.iter() { if ins.r#type != "invalid" { let opcode = ins - .opcode + .disasm .as_ref() .unwrap() .split_whitespace() From babe29c60fe65fe392aee175061e31a10a7db262 Mon Sep 17 00:00:00 2001 From: br0kej Date: Tue, 20 Aug 2024 11:42:47 +0100 Subject: [PATCH 4/5] [feature] Adding support for creating CFG's with pseudo code nodes --- src/agfj.rs | 76 ++++++++++++++++++++++++++++++++++++------------- src/bb.rs | 16 ++++++++++- src/main.rs | 4 ++- src/networkx.rs | 40 +++++++++++++++++++++++++- 4 files changed, 114 insertions(+), 22 deletions(-) diff --git a/src/agfj.rs b/src/agfj.rs index c5490a0..97c022d 100644 --- a/src/agfj.rs +++ b/src/agfj.rs @@ -1,9 +1,7 @@ use crate::bb::{ACFJBlock, FeatureType, TikNibFeaturesBB}; #[cfg(feature = "inference")] use crate::inference::InferenceJob; -use crate::networkx::{ - DGISNode, DisasmNode, DiscovreNode, EsilNode, GeminiNode, NetworkxDiGraph, NodeType, TiknibNode, -}; +use crate::networkx::{DGISNode, DisasmNode, DiscovreNode, EsilNode, GeminiNode, NetworkxDiGraph, NodeType, PseudoNode, TiknibNode}; use crate::utils::{average, check_or_create_dir, get_save_file_path}; use enum_as_inner::EnumAsInner; use itertools::Itertools; @@ -110,6 +108,29 @@ impl AGFJFunc { } } + pub fn get_psuedo_function_string( + &self, + min_blocks: &u16, + reg_norm: bool, + ) -> Option<(String, String)> { + let mut psuedo_function = Vec::::new(); + if self.blocks.len() >= (*min_blocks).into() && self.blocks[0].offset != 1 { + for bb in &self.blocks { + let psuedo: Vec = bb.get_psuedo_bb(reg_norm); + for ins in psuedo.iter() { + if !ins.is_empty() { + let split: Vec = ins.split(',').map(|s| s.to_string()).collect(); + let split_joined = split.join(" "); + psuedo_function.push(split_joined); + } + } + } + let joined = psuedo_function.join(" "); + Some((self.name.clone(), joined)) + } else { + None + } + } pub fn create_bb_edge_list(&mut self, min_blocks: &u16) { if self.blocks.len() > (*min_blocks).into() && self.blocks[0].offset != 1 { let mut addr_idxs = Vec::::new(); @@ -371,12 +392,11 @@ impl AGFJFunc { | FeatureType::Gemini | FeatureType::DiscovRE | FeatureType::DGIS => StringOrF64::F64(Vec::new()), - FeatureType::Esil | FeatureType::Disasm => StringOrF64::String(Vec::new()), + FeatureType::Esil | FeatureType::Disasm | FeatureType::Pseudo | FeatureType::Pcode => StringOrF64::String(Vec::new()), FeatureType::ModelEmbedded | FeatureType::Encoded | FeatureType::Invalid => { info!("Invalid Feature Type. Skipping.."); return; } - FeatureType::Pcode => StringOrF64::String(Vec::new()), }; let min_offset: u64 = self.offset; @@ -397,7 +417,7 @@ impl AGFJFunc { bb.generate_bb_feature_vec(feature_vecs, feature_type, architecture); } } - FeatureType::Esil | FeatureType::Disasm => { + FeatureType::Esil | FeatureType::Disasm | FeatureType::Pseudo => { let feature_vecs = feature_vecs.as_string_mut().unwrap(); for bb in &self.blocks { bb.get_block_edges( @@ -408,6 +428,7 @@ impl AGFJFunc { ); bb.generate_bb_feature_strings(feature_vecs, feature_type, true); } + debug!("Number of Feature Vecs: {}", feature_vecs.len()) } FeatureType::ModelEmbedded | FeatureType::Encoded | FeatureType::Invalid => { info!("Invalid Feature Type. Skipping.."); @@ -416,6 +437,7 @@ impl AGFJFunc { _ => {} }; + debug!("Edge List Empty: {} Edge List Dims: {}", edge_list.is_empty(), edge_list.len()); if !edge_list.is_empty() { let mut graph = Graph::::from_edges(&edge_list); @@ -438,7 +460,7 @@ impl AGFJFunc { &File::create(fname_string).expect("Failed to create writer"), &networkx_graph_inners, ) - .expect("Unable to write JSON"); + .expect("Unable to write JSON"); } else if feature_type == FeatureType::DGIS { let networkx_graph: NetworkxDiGraph = NetworkxDiGraph::::from(( @@ -454,7 +476,7 @@ impl AGFJFunc { &File::create(fname_string).expect("Failed to create writer"), &networkx_graph_inners, ) - .expect("Unable to write JSON"); + .expect("Unable to write JSON"); } else if feature_type == FeatureType::DiscovRE { let networkx_graph: NetworkxDiGraph = NetworkxDiGraph::::from(( @@ -470,7 +492,7 @@ impl AGFJFunc { &File::create(fname_string).expect("Failed to create writer"), &networkx_graph_inners, ) - .expect("Unable to write JSON"); + .expect("Unable to write JSON"); } else if feature_type == FeatureType::Tiknib { let networkx_graph: NetworkxDiGraph = NetworkxDiGraph::::from(( @@ -486,7 +508,7 @@ impl AGFJFunc { &File::create(fname_string).expect("Failed to create writer"), &networkx_graph_inners, ) - .expect("Unable to write JSON"); + .expect("Unable to write JSON"); } else if feature_type == FeatureType::Disasm { let networkx_graph: NetworkxDiGraph = NetworkxDiGraph::::from(( @@ -502,7 +524,7 @@ impl AGFJFunc { &File::create(fname_string).expect("Failed to create writer"), &networkx_graph_inners, ) - .expect("Unable to write JSON"); + .expect("Unable to write JSON"); } else if feature_type == FeatureType::Esil { let networkx_graph: NetworkxDiGraph = NetworkxDiGraph::::from(( @@ -518,22 +540,38 @@ impl AGFJFunc { &File::create(fname_string).expect("Failed to create writer"), &networkx_graph_inners, ) - .expect("Unable to write JSON"); + .expect("Unable to write JSON"); + } else if feature_type == FeatureType::Pseudo { + let networkx_graph: NetworkxDiGraph = + NetworkxDiGraph::::from(( + &graph, + feature_vecs.as_string().unwrap(), + feature_type, + )); + + let networkx_graph_inners: NetworkxDiGraph = + NetworkxDiGraph::::from(networkx_graph); + info!("Saving to JSON.."); + serde_json::to_writer( + &File::create(fname_string).expect("Failed to create writer"), + &networkx_graph_inners, + ) + .expect("Unable to write JSON"); + } else { + info!("Function {} has no edges. Skipping...", self.name) } } else { - info!("Function {} has no edges. Skipping...", self.name) - } - } else { - info!( + info!( "Function {} has less than the minimum number of blocks. Skipping..", self.name ); - } - } else { - info!( + } + } else { + info!( "Function {} has already been processed. Skipping...", self.name ) + } } } diff --git a/src/bb.rs b/src/bb.rs index ba3c4a7..232b01b 100644 --- a/src/bb.rs +++ b/src/bb.rs @@ -26,6 +26,7 @@ pub enum FeatureType { Encoded, Invalid, Pcode, + Pseudo } impl fmt::Display for FeatureType { @@ -41,6 +42,7 @@ impl fmt::Display for FeatureType { FeatureType::Encoded => "encoded", FeatureType::Invalid => "invalid", FeatureType::Pcode => "pcode", + FeatureType::Pseudo => "pseudo" }; write!(f, "{}", feature_type_str) } @@ -255,9 +257,9 @@ impl ACFJBlock { let feature_vector: Vec = match feature_type { FeatureType::Disasm => self.get_disasm_bb(normalise), FeatureType::Esil => self.get_esil_bb(normalise), + FeatureType::Pseudo => self.get_psuedo_bb(normalise), _ => unreachable!(), }; - if feature_vector.is_empty() { error!("Empty feature vector. This means that the feature type is wrong!") } else { @@ -541,6 +543,18 @@ impl ACFJBlock { disasm_ins } + pub fn get_psuedo_bb(&self, reg_norm: bool) -> Vec { + let mut psuedo_ins: Vec = Vec::new(); + for op in &self.ops { + if op.opcode.is_some() && op.opcode.as_ref().unwrap().len() > 1 { + let opcode_single = &op.opcode.as_ref().unwrap(); + let normd = normalise_disasm_simple(opcode_single, reg_norm); + psuedo_ins.push((*normd).to_string()); + } + } + psuedo_ins + } + pub fn get_ins(&self, reg_norm: bool) -> Vec { let mut disasm_ins: Vec = Vec::new(); for op in &self.ops { diff --git a/src/main.rs b/src/main.rs index f9e3a59..0a8f35c 100644 --- a/src/main.rs +++ b/src/main.rs @@ -114,7 +114,7 @@ enum GenerateSubCommands { output_path: PathBuf, /// The type of features to generate per basic block (node) - #[arg(short, long, value_name = "FEATURE_TYPE", value_parser = clap::builder::PossibleValuesParser::new(["gemini", "discovre", "dgis", "tiknib", "disasm", "esil", "pcode"]) + #[arg(short, long, value_name = "FEATURE_TYPE", value_parser = clap::builder::PossibleValuesParser::new(["gemini", "discovre", "dgis", "tiknib", "disasm", "esil", "pcode", "pseudo"]) .map(|s| s.parse::().unwrap()),)] feature_type: Option, @@ -461,6 +461,7 @@ fn main() { #[cfg(feature = "inference")] "embed" => FeatureType::ModelEmbedded, "pcode" => FeatureType::Pcode, + "pseudo" => FeatureType::Pseudo, _ => FeatureType::Invalid, }; @@ -473,6 +474,7 @@ fn main() { || feature_vec_type == FeatureType::Tiknib || feature_vec_type == FeatureType::Disasm || feature_vec_type == FeatureType::Esil + || feature_vec_type == FeatureType::Pseudo { info!( "Creating graphs with {:?} feature vectors.", diff --git a/src/networkx.rs b/src/networkx.rs index e4f8a33..f758132 100644 --- a/src/networkx.rs +++ b/src/networkx.rs @@ -50,6 +50,7 @@ pub enum NodeType { Disasm(DisasmNode), Esil(EsilNode), PCode(PCodeNode), + Pseudo(PseudoNode) } #[derive(Debug, Clone, PartialEq, Hash, Serialize, Deserialize, EnumAsInner)] @@ -110,6 +111,22 @@ impl From<(i64, &Vec)> for EsilNode { } } +#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)] +pub struct PseudoNode { + pub id: i64, + pub features: Vec +} + +impl From<(i64, &Vec)> for PseudoNode { + fn from(src: (i64, &Vec)) -> PseudoNode { + PseudoNode { + id: src.0, + features: src.1.to_owned(), + } + } +} + + #[derive(Copy, Default, Debug, Clone, PartialEq, Serialize, Deserialize)] pub struct TiknibNode { pub id: i64, @@ -434,7 +451,8 @@ impl From<(&Graph, &Vec>, FeatureType)> for NetworkxDiG Some(NodeType::Disasm(DisasmNode::from((i as i64, node_vector)))) } FeatureType::Esil => Some(NodeType::Esil(EsilNode::from((i as i64, node_vector)))), - _ => None, + FeatureType::Pseudo => Some(NodeType::Pseudo(PseudoNode::from((i as i64, node_vector)))), + _ => todo!() }; if let Some(node) = node { nodes.push(node); @@ -641,6 +659,26 @@ impl From> for NetworkxDiGraph { } } +impl From> for NetworkxDiGraph { + fn from(src: NetworkxDiGraph) -> NetworkxDiGraph { + let inner_nodes_types: Vec = src + .clone() + .nodes + .into_iter() + .map(|el| el.as_pseudo().unwrap().clone()) + .collect(); + + NetworkxDiGraph { + adjacency: src.adjacency, + directed: src.directed, + graph: vec![], + multigraph: false, + nodes: inner_nodes_types, + } + } +} + + #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)] pub struct PCodeNode { pub id: u64, From f7834043bf37aa43c954e953342b83915c6a55fa Mon Sep 17 00:00:00 2001 From: br0kej Date: Tue, 20 Aug 2024 11:52:17 +0100 Subject: [PATCH 5/5] cargo fmt --- src/agfj.rs | 50 ++++++++++++++++++++++++++++++++----------------- src/bb.rs | 4 ++-- src/extract.rs | 7 ++++++- src/networkx.rs | 12 ++++++------ 4 files changed, 47 insertions(+), 26 deletions(-) diff --git a/src/agfj.rs b/src/agfj.rs index 97c022d..8d64697 100644 --- a/src/agfj.rs +++ b/src/agfj.rs @@ -1,7 +1,10 @@ use crate::bb::{ACFJBlock, FeatureType, TikNibFeaturesBB}; #[cfg(feature = "inference")] use crate::inference::InferenceJob; -use crate::networkx::{DGISNode, DisasmNode, DiscovreNode, EsilNode, GeminiNode, NetworkxDiGraph, NodeType, PseudoNode, TiknibNode}; +use crate::networkx::{ + DGISNode, DisasmNode, DiscovreNode, EsilNode, GeminiNode, NetworkxDiGraph, NodeType, + PseudoNode, TiknibNode, +}; use crate::utils::{average, check_or_create_dir, get_save_file_path}; use enum_as_inner::EnumAsInner; use itertools::Itertools; @@ -361,7 +364,13 @@ impl AGFJFunc { feature_type: FeatureType, architecture: &String, ) { - let full_output_path = get_save_file_path(path, output_path, None, Some(feature_type.to_string()), None); + let full_output_path = get_save_file_path( + path, + output_path, + None, + Some(feature_type.to_string()), + None, + ); check_or_create_dir(&full_output_path); let file_name = path.file_name().unwrap(); let binding = file_name.to_string_lossy().to_string(); @@ -392,7 +401,10 @@ impl AGFJFunc { | FeatureType::Gemini | FeatureType::DiscovRE | FeatureType::DGIS => StringOrF64::F64(Vec::new()), - FeatureType::Esil | FeatureType::Disasm | FeatureType::Pseudo | FeatureType::Pcode => StringOrF64::String(Vec::new()), + FeatureType::Esil + | FeatureType::Disasm + | FeatureType::Pseudo + | FeatureType::Pcode => StringOrF64::String(Vec::new()), FeatureType::ModelEmbedded | FeatureType::Encoded | FeatureType::Invalid => { info!("Invalid Feature Type. Skipping.."); return; @@ -437,7 +449,11 @@ impl AGFJFunc { _ => {} }; - debug!("Edge List Empty: {} Edge List Dims: {}", edge_list.is_empty(), edge_list.len()); + debug!( + "Edge List Empty: {} Edge List Dims: {}", + edge_list.is_empty(), + edge_list.len() + ); if !edge_list.is_empty() { let mut graph = Graph::::from_edges(&edge_list); @@ -460,7 +476,7 @@ impl AGFJFunc { &File::create(fname_string).expect("Failed to create writer"), &networkx_graph_inners, ) - .expect("Unable to write JSON"); + .expect("Unable to write JSON"); } else if feature_type == FeatureType::DGIS { let networkx_graph: NetworkxDiGraph = NetworkxDiGraph::::from(( @@ -476,7 +492,7 @@ impl AGFJFunc { &File::create(fname_string).expect("Failed to create writer"), &networkx_graph_inners, ) - .expect("Unable to write JSON"); + .expect("Unable to write JSON"); } else if feature_type == FeatureType::DiscovRE { let networkx_graph: NetworkxDiGraph = NetworkxDiGraph::::from(( @@ -492,7 +508,7 @@ impl AGFJFunc { &File::create(fname_string).expect("Failed to create writer"), &networkx_graph_inners, ) - .expect("Unable to write JSON"); + .expect("Unable to write JSON"); } else if feature_type == FeatureType::Tiknib { let networkx_graph: NetworkxDiGraph = NetworkxDiGraph::::from(( @@ -508,7 +524,7 @@ impl AGFJFunc { &File::create(fname_string).expect("Failed to create writer"), &networkx_graph_inners, ) - .expect("Unable to write JSON"); + .expect("Unable to write JSON"); } else if feature_type == FeatureType::Disasm { let networkx_graph: NetworkxDiGraph = NetworkxDiGraph::::from(( @@ -524,7 +540,7 @@ impl AGFJFunc { &File::create(fname_string).expect("Failed to create writer"), &networkx_graph_inners, ) - .expect("Unable to write JSON"); + .expect("Unable to write JSON"); } else if feature_type == FeatureType::Esil { let networkx_graph: NetworkxDiGraph = NetworkxDiGraph::::from(( @@ -540,7 +556,7 @@ impl AGFJFunc { &File::create(fname_string).expect("Failed to create writer"), &networkx_graph_inners, ) - .expect("Unable to write JSON"); + .expect("Unable to write JSON"); } else if feature_type == FeatureType::Pseudo { let networkx_graph: NetworkxDiGraph = NetworkxDiGraph::::from(( @@ -556,21 +572,21 @@ impl AGFJFunc { &File::create(fname_string).expect("Failed to create writer"), &networkx_graph_inners, ) - .expect("Unable to write JSON"); + .expect("Unable to write JSON"); } else { info!("Function {} has no edges. Skipping...", self.name) } } else { info!( - "Function {} has less than the minimum number of blocks. Skipping..", - self.name - ); + "Function {} has less than the minimum number of blocks. Skipping..", + self.name + ); } } else { info!( - "Function {} has already been processed. Skipping...", - self.name - ) + "Function {} has already been processed. Skipping...", + self.name + ) } } } diff --git a/src/bb.rs b/src/bb.rs index 232b01b..39ba083 100644 --- a/src/bb.rs +++ b/src/bb.rs @@ -26,7 +26,7 @@ pub enum FeatureType { Encoded, Invalid, Pcode, - Pseudo + Pseudo, } impl fmt::Display for FeatureType { @@ -42,7 +42,7 @@ impl fmt::Display for FeatureType { FeatureType::Encoded => "encoded", FeatureType::Invalid => "invalid", FeatureType::Pcode => "pcode", - FeatureType::Pseudo => "pseudo" + FeatureType::Pseudo => "pseudo", }; write!(f, "{}", feature_type_str) } diff --git a/src/extract.rs b/src/extract.rs index 3b4a9ef..d0a3733 100644 --- a/src/extract.rs +++ b/src/extract.rs @@ -903,7 +903,12 @@ impl FileToBeProcessed { debug!("Creating r2 handle without debugging"); R2PipeSpawnOptions { exepath: "radare2".to_owned(), - args: vec!["-e bin.cache=true", "-e log.level=1", "-2", "-e asm.pseudo=true"], + args: vec![ + "-e bin.cache=true", + "-e log.level=1", + "-2", + "-e asm.pseudo=true", + ], } }; diff --git a/src/networkx.rs b/src/networkx.rs index f758132..84b55b2 100644 --- a/src/networkx.rs +++ b/src/networkx.rs @@ -50,7 +50,7 @@ pub enum NodeType { Disasm(DisasmNode), Esil(EsilNode), PCode(PCodeNode), - Pseudo(PseudoNode) + Pseudo(PseudoNode), } #[derive(Debug, Clone, PartialEq, Hash, Serialize, Deserialize, EnumAsInner)] @@ -114,7 +114,7 @@ impl From<(i64, &Vec)> for EsilNode { #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)] pub struct PseudoNode { pub id: i64, - pub features: Vec + pub features: Vec, } impl From<(i64, &Vec)> for PseudoNode { @@ -126,7 +126,6 @@ impl From<(i64, &Vec)> for PseudoNode { } } - #[derive(Copy, Default, Debug, Clone, PartialEq, Serialize, Deserialize)] pub struct TiknibNode { pub id: i64, @@ -451,8 +450,10 @@ impl From<(&Graph, &Vec>, FeatureType)> for NetworkxDiG Some(NodeType::Disasm(DisasmNode::from((i as i64, node_vector)))) } FeatureType::Esil => Some(NodeType::Esil(EsilNode::from((i as i64, node_vector)))), - FeatureType::Pseudo => Some(NodeType::Pseudo(PseudoNode::from((i as i64, node_vector)))), - _ => todo!() + FeatureType::Pseudo => { + Some(NodeType::Pseudo(PseudoNode::from((i as i64, node_vector)))) + } + _ => todo!(), }; if let Some(node) = node { nodes.push(node); @@ -678,7 +679,6 @@ impl From> for NetworkxDiGraph { } } - #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)] pub struct PCodeNode { pub id: u64,