diff --git a/Cargo.toml b/Cargo.toml index d42ae29..be4b6dc 100755 --- a/Cargo.toml +++ b/Cargo.toml @@ -23,7 +23,7 @@ smda = { git = "https://github.com/marirs/smda-rs.git" } thiserror = "2.0.12" walkdir = "2.5.0" yaml-rust = "0.4.5" -goblin = { version = "0.9.3", features = ["alloc"] } +goblin = { version = "0.10.0", features = ["alloc"] } maplit = "1.0.2" dnfile = { git = "https://github.com/marirs/dnfile-rs.git", branch = "master" } lazy_static = "1.5.0" diff --git a/src/consts.rs b/src/consts.rs index f1618aa..94d4919 100644 --- a/src/consts.rs +++ b/src/consts.rs @@ -41,6 +41,8 @@ pub enum Os { FENIXOS, CLOUD, UNDEFINED, + ANDROID, + #[allow(non_camel_case_types)] ARCH_SPECIFIC, } @@ -66,11 +68,12 @@ impl Display for Os { Os::FENIXOS => write!(f, "FenixOS"), Os::CLOUD => write!(f, "Cloud"), Os::UNDEFINED => write!(f, "undefined"), + Os::ANDROID => write!(f, "Android"), Os::ARCH_SPECIFIC => write!(f, "Architecture-specific"), } } } - +#[allow(dead_code)] #[derive(Debug)] pub enum Endian { _Big, diff --git a/src/error.rs b/src/error.rs index 02a0afd..82ffc80 100644 --- a/src/error.rs +++ b/src/error.rs @@ -100,4 +100,6 @@ pub enum Error { NoiImplementedError, #[error("Buffer overflow error")] BufferOverflowError, + #[error("Match rule not found: {0}")] + MatchRuleNotFound(String), } diff --git a/src/extractor/dnfile.rs b/src/extractor/dnfile.rs index 31210db..2a2f0bb 100644 --- a/src/extractor/dnfile.rs +++ b/src/extractor/dnfile.rs @@ -258,6 +258,8 @@ impl super::Extractor for Extractor { ss.extend(self.extract_insn_namespace_features(&f.f, &insn.i)?); ss.extend(self.extract_insn_class_features(&f.f, &insn.i)?); ss.extend(self.extract_unmanaged_call_characteristic_features(&f.f, &insn.i)?); + ss.extend(self.extract_file_format()?); + ss.extend(self.extract_global_features()?); Ok(ss) } } diff --git a/src/extractor/smda.rs b/src/extractor/smda.rs index 91aba3e..2ca6550 100644 --- a/src/extractor/smda.rs +++ b/src/extractor/smda.rs @@ -1,6 +1,16 @@ #![allow(dead_code, clippy::to_string_in_format_args)] use std::collections::{BTreeMap, HashMap}; +use lazy_static::lazy_static; +use regex::Regex; + +lazy_static! { + static ref RE_NUMBER_HEX_SPACED: Regex = Regex::new(r"(?P[+\-]) (?P0x[a-fA-F0-9]+)").unwrap(); + static ref RE_NUMBER_INT_SPACED: Regex = Regex::new(r"(?P[+\-]) (?P[0-9]+)").unwrap(); + + static ref RE_NUMBER_HEX: Regex = Regex::new(r"(?P[+\-])(?P0x[a-fA-F0-9]+)").unwrap(); + static ref RE_NUMBER_INT: Regex = Regex::new(r"(?P[+\-])(?P[0-9]+)").unwrap(); +} use smda::{ function::{Function, Instruction}, @@ -236,6 +246,8 @@ impl super::Extractor for Extractor { res.extend(self.extract_insn_segment_access_features(&f.f, &insn.i)?); res.extend(self.extract_function_calls_from(&f.f, &insn.i)?); res.extend(self.extract_function_indirect_call_characteristic_features(&f.f, &insn.i)?); + res.extend(self.extract_file_format()?); + res.extend(self.extract_global_features()?); Ok(res) } } @@ -264,9 +276,8 @@ impl Extractor { } pub fn get_elf_os(elf: &goblin::elf::Elf) -> Result { - // eprintln!("{}", elf.header.e_ident[7]); match elf.header.e_ident[7] { - 0x00 => Ok(Os::UNDEFINED), + 0x00 => Ok(Os::LINUX), 0x01 => Ok(Os::HPUX), 0x02 => Ok(Os::NETBSD), 0x03 => Ok(Os::LINUX), @@ -283,7 +294,10 @@ impl Extractor { 0x0F => Ok(Os::AROS), 0x10 => Ok(Os::FENIXOS), 0x11 => Ok(Os::CLOUD), - _ => Ok(Os::ARCH_SPECIFIC), + _ => { + // For Unknown ELF OS, also assume Linux as fallback + Ok(Os::LINUX) + } } } pub fn extract_os(&self) -> Result { @@ -692,7 +706,7 @@ impl Extractor { if let Some(o) = &insn.operands { let operands: Vec = o.split(',').map(|s| s.trim().to_string()).collect(); for (i, operand) in operands.iter().enumerate() { - if !operand.contains("ptr") { + if !insn.mnemonic.contains("lea") && !operand.contains("ptr") { continue; } //NOTE not sure @@ -702,11 +716,9 @@ impl Extractor { continue; } let mut number = 0; - let re_number_hex = - regex::Regex::new(r"(?P[+\-]) (?P0x[a-fA-F0-9]+)").unwrap(); - let re_number_int = regex::Regex::new(r"(?P[+\-]) (?P[0-9])").unwrap(); - let number_hex = re_number_hex.captures(operand); - let number_int = re_number_int.captures(operand); + + let number_hex = RE_NUMBER_HEX_SPACED.captures(operand); + let number_int = RE_NUMBER_INT_SPACED.captures(operand); if let Some(n) = number_hex { number = i128::from_str_radix(&n["num"][2..], 16)?; if &n["sign"] == "-" { @@ -730,6 +742,15 @@ impl Extractor { ), insn.offset, )); + + if insn.mnemonic.contains("lea") { + res.push(( + crate::rules::features::Feature::Number( + crate::rules::features::NumberFeature::new(f.bitness, &number, "")?, + ), + insn.offset, + )); + } } } Ok(res) @@ -790,13 +811,42 @@ impl Extractor { } fn parse_operand_to_number(&self, operand: &str) -> Option { + let operand = operand.trim(); + + // case 1: if operand is like 0x1234 if let Some(x) = operand.strip_prefix("0x") { - i128::from_str_radix(x, 16).ok() - } else if let Some(stripped_operand) = operand.strip_suffix('h') { - i128::from_str_radix(stripped_operand, 16).ok() - } else { - i128::from_str_radix(operand, 16).ok() + return i128::from_str_radix(x, 16).ok(); + } + + // case 2: if operand is like 1234h + if let Some(stripped_operand) = operand.strip_suffix('h') { + return i128::from_str_radix(stripped_operand, 16).ok(); + } + + // case 3: if operand is like +0x1234 + //if start with sign apply this logic + if operand.starts_with('-') || operand.starts_with('+') { + if let Some(captures) = RE_NUMBER_HEX.captures(operand) { + let sign = &captures["sign"]; + let number = &captures["num"]; + let value = i128::from_str_radix(number, 16).ok()?; + return Some(if sign == "-" { -value } else { value }); + } + if let Some(captures) = RE_NUMBER_INT.captures(operand) { + let sign = &captures["sign"]; + let number = &captures["num"]; + let value = number.parse::().ok()?; + return Some(if sign == "-" { -value } else { value }); + } } + + // case 4: if operand is like 1234 + if let Ok(val) = operand.parse::() { + return Some(val); + } + + // case 5: if operand is like 0x1234 + i128::from_str_radix(operand, 16).ok() } pub fn extract_insn_number_features( @@ -814,12 +864,25 @@ impl Extractor { for (i, operand) in operands.iter().enumerate() { if let Some(s) = self.parse_operand_to_number(operand) { - res.push(( - crate::rules::features::Feature::Number( - crate::rules::features::NumberFeature::new(f.bitness, &s, "")?, - ), - insn.offset, - )); + if s >= 0 + { + res.push(( + crate::rules::features::Feature::Number( + crate::rules::features::NumberFeature::new(f.bitness, &s, "")?, + ), + insn.offset, + )); + } + else + { + let masked_value = (s as u32) as i128; // Convierte a u32 y de vuelta a i128 + res.push(( + crate::rules::features::Feature::Number( + crate::rules::features::NumberFeature::new(f.bitness, &masked_value, "")?, + ), + insn.offset, + )); + } res.push(( crate::rules::features::Feature::OperandNumber( crate::rules::features::OperandNumberFeature::new(&i, &s, "")?, @@ -838,52 +901,127 @@ impl Extractor { insn: &Instruction, ) -> Result> { let mut res = vec![]; - if f.apirefs.contains_key(&insn.offset) { - let (dll, api) = &f.apirefs[&insn.offset]; - for name in generate_symbols(dll, api)? { - res.push(( - crate::rules::features::Feature::Api(crate::rules::features::ApiFeature::new( - &name, "", - )?), - insn.offset, - )); + let va = self.report.base_addr + insn.offset; + + if let Some((dll, api)) = f.apirefs.get(&insn.offset) { + if let Some(api_name) = api { + let highest_addr = self.find_highest_address_for_symbol(api_name); + if let Some(addr) = highest_addr { + self.push_api_features(&mut res, dll, api, addr)?; + } else { + self.push_api_features(&mut res, dll, api, va)?; + } + } else { + self.push_api_features(&mut res, dll, api, va)?; + } + return Ok(res); + } + + if let Some(targets) = f.outrefs.get(&insn.offset) { + let mut api_candidates = Vec::new(); + + for target in targets.iter().rev() { + if let Some((dll, api)) = self.report.addr_to_api.get(target) { + api_candidates.push((*target, dll.clone(), api.clone())); + } + } + + if !api_candidates.is_empty() { + let best_candidate = api_candidates.iter().max_by_key(|(addr, _, _)| *addr).unwrap(); + let (mut best_addr, dll, api) = best_candidate.clone(); + + // FIX: api es Option, desenvolverlo + if let Some(api_name) = &api { + if let Some(highest) = self.find_highest_address_for_symbol(api_name) { + best_addr = highest; + } + } + + self.push_api_features(&mut res, &dll, &api, best_addr)?; + return Ok(res); } - } else if f.outrefs.contains_key(&insn.offset) { - let mut current_function = f; - let mut current_instruction = insn; - for _index in 0..5 { - //}THUNK_CHAIN_DEPTH_DELTA - if current_function.outrefs[¤t_instruction.offset].len() == 1 { - let target = current_function.outrefs[¤t_instruction.offset][0]; - if let Ok(referenced_function) = self.report.get_function(target) { - //# TODO SMDA: implement this function for both jmp and call, checking if function has 1 instruction which refs an API - if referenced_function.is_api_thunk()? { - if referenced_function.apirefs.contains_key(&target) { - let (dll, api) = &referenced_function.apirefs[&target]; - for name in generate_symbols(dll, api)? { - res.push(( - crate::rules::features::Feature::Api( - crate::rules::features::ApiFeature::new(&name, "")?, - ), - insn.offset, - )); - } + } + + // fallback: thunk chain resolution + let mut current_function = f; + let mut current_instruction = insn; + + for _ in 0..5 { + if let Some(targets) = current_function.outrefs.get(¤t_instruction.offset) { + if targets.len() != 1 { + break; + } + + let chain_target = targets[0]; + let referenced_function = match self.report.get_function(chain_target) { + Ok(func) => func, + Err(_) => break, + }; + + if referenced_function.is_api_thunk()? { + if let Some((dll, api)) = referenced_function.apirefs.get(&chain_target) { + if let Some(api_name) = api { + if let Some(highest) = self.find_highest_address_for_symbol(api_name) { + self.push_api_features(&mut res, dll, api, highest)?; + return Ok(res); } - } else if referenced_function.get_num_instructions()? == 1 - && referenced_function.get_num_outrefs()? == 1 - { - current_function = referenced_function; - current_instruction = referenced_function.get_instructions()?[0]; } - } else { - return Ok(res); + + self.push_api_features(&mut res, dll, api, chain_target)?; } + break; + } + + if referenced_function.get_num_instructions()? == 1 + && referenced_function.get_num_outrefs()? == 1 + { + current_function = referenced_function; + current_instruction = referenced_function.get_instructions()?[0]; + } + else + { + break; } + } else { + break; } } + Ok(res) } + fn find_highest_address_for_symbol(&self, symbol_name: &str) -> Option { + let mut addresses = Vec::new(); + + for (addr, (_, api)) in &self.report.addr_to_api { + if let Some(api_name) = api { + if api_name == symbol_name { + addresses.push(*addr); + } + } + } + + addresses.into_iter().max() + } + + fn push_api_features( + &self, + res: &mut Vec<(crate::rules::features::Feature, u64)>, + dll: &Option, + api: &Option, + va: u64, + ) -> Result<()> { + for name in generate_symbols(dll, api)? { + res.push(( + crate::rules::features::Feature::Api( + crate::rules::features::ApiFeature::new(&name, "")?, + ), + va, + )); + } + Ok(()) + } + fn xor_static(data: &[u8], i: u8) -> Result> { let mut res = vec![]; for c in data { @@ -950,40 +1088,47 @@ impl Extractor { let pblen = pbytes.len(); let mut todo = vec![]; + + // Buscar patrones MZ XORed for (mzx, pex, key) in mz_xor { - if let Some(ff) = pbytes[offset as usize..] + if let Some(relative_pos) = pbytes[offset as usize..] .windows(mzx.len()) .position(|window| window == mzx) { - todo.push((ff, mzx, pex, key)); + let absolute_pos = offset as usize + relative_pos; // ← CORREGIR: posición absoluta + todo.push((absolute_pos, mzx, pex, key)); } } + let mut res = vec![]; while let Some((off, mzx, pex, key)) = todo.pop() { - // println!("{}", todo.len()); - - //The MZ header has one field we will check - //e_lfanew is at 0x3c - let e_lfanew = off + 0x3C; - if pblen < (e_lfanew + 4) { + // The MZ header has one field we will check + let e_lfanew_offset = off + 0x3C; + if e_lfanew_offset + 4 > pblen { continue; } - let ppp: [u8; 4] = Extractor::xor_static(&pbytes[e_lfanew..e_lfanew + 4], key)? + let ppp: [u8; 4] = Extractor::xor_static(&pbytes[e_lfanew_offset..e_lfanew_offset + 4], key)? .try_into() - .map_err(|_| Error::InvalidRule(line!(), "aaa".to_string()))?; - let newoff = u32::from_le_bytes(ppp); - if let Some(ff) = pbytes[off + 1..] - .windows(mzx.len()) - .position(|window| window == mzx) - { - todo.push((ff, mzx, pex.clone(), key)); + .map_err(|_| Error::InvalidRule(line!(), "Failed to parse e_lfanew".to_string()))?; + + let pe_offset_value = u32::from_le_bytes(ppp); + + if off + 1 < pblen { + if let Some(relative_pos) = pbytes[off + 1..] + .windows(mzx.len()) + .position(|window| window == mzx) + { + let next_absolute_pos = off + 1 + relative_pos; // ← CORREGIR: posición absoluta + todo.push((next_absolute_pos, mzx, pex.clone(), key)); + } } - let peoff = off + newoff as usize; - if pblen < (peoff + 2) { + + let pe_header_offset = off + pe_offset_value as usize; + if pe_header_offset + 2 > pblen { continue; } - if pbytes[peoff..peoff + 2] == pex { + if pbytes[pe_header_offset..pe_header_offset + 2] == pex { res.push((off as u64, key as u64)); } } @@ -1021,32 +1166,49 @@ pub fn get_operands(ins: &Instruction) -> Result<(String, String)> { Ok(("".to_string(), "".to_string())) } +fn clean_dll_name(dll_name: &str) -> String { + let mut clean = dll_name.to_string(); + + // Remove common prefixes + if clean.ends_with(".so.6") { + clean = clean[..clean.len() - 5].to_string(); + } else if clean.ends_with(".so") { + clean = clean[..clean.len() - 3].to_string(); + } else if clean.ends_with(".dll") { + clean = clean[..clean.len() - 4].to_string(); + } + + clean +} + + pub fn generate_symbols(dll: &Option, symbol: &Option) -> Result> { let mut res = vec![]; - let mut dll_name = dll - .clone() - .ok_or_else(|| Error::InvalidRule(line!(), file!().to_string()))?; - if dll_name.ends_with(".dll") { - dll_name = dll_name[..dll_name.len() - 4].to_string(); - } - let symbol_name = symbol - .clone() - .ok_or_else(|| Error::InvalidRule(line!(), file!().to_string()))?; - res.push(format!("{}.{}", dll_name, symbol_name)); - - if &symbol_name[..1] != "#" { + let symbol_name = symbol.clone().ok_or_else(|| Error::InvalidRule(line!(), file!().to_string()))?; + + // Add simple symbol if it does not start with # + if !symbol_name.starts_with('#') { res.push(symbol_name.clone()); } - //TODO - if symbol_name.ends_with('A') || symbol_name.ends_with('W') { - res.push(format!( - "{}.{}", - dll_name, - symbol_name[..symbol_name.len() - 1].to_string() - )); - if &symbol_name[..1] != "#" { - res.push(symbol_name[..symbol_name.len() - 1].to_string()); + // DLL.symbol + if let Some(dll_ref) = dll { + let dll_clean = clean_dll_name(dll_ref); + let dll_symbol = format!("{}.{}", dll_clean, symbol_name); + if dll_symbol != symbol_name { + res.push(dll_symbol); + } + } + + // A/W variants para APIs Windows + if !symbol_name.starts_with("_Z") && (symbol_name.ends_with('A') || symbol_name.ends_with('W')) { + let base_name = &symbol_name[..symbol_name.len() - 1]; + if !res.contains(&base_name.to_string()) { + res.push(base_name.to_string()); + } + if let Some(dll_ref) = dll { + let dll_clean = clean_dll_name(dll_ref); + res.push(format!("{}.{}", dll_clean, base_name)); } } Ok(res) @@ -1142,7 +1304,7 @@ pub fn detect_ascii_len(report: &DisassemblyReport, offset: &u64) -> Result?@[\\]^_`{|}~ ".contains(&ch)) + .take_while(|&&ch| b"0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!\"#$%&'()*+, -./:;<=>?@[\\]^_`{|}~ \r\n".contains(&ch)) .count(); if rva + ascii_len as u64 >= buffer_len { @@ -1160,7 +1322,7 @@ pub fn detect_unicode_len(report: &DisassemblyReport, offset: &u64) -> Result?@[\\]^_`{|}~ ".contains(&ch) && second_char == 0{ + while ch < 127 && b"0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!\"#$%&'()*+, -./:;<=>?@[\\]^_`{|}~ \r\n".contains(&ch) && second_char == 0{ unicode_len += 2; rva += 2; ch = report.buffer[rva as usize]; diff --git a/src/lib.rs b/src/lib.rs index 9ba9edb..e7043b4 100755 --- a/src/lib.rs +++ b/src/lib.rs @@ -202,7 +202,10 @@ impl FileCapabilities { let (format, buffer) = get_format(&f)?; let extractor = get_file_extractors(&f, format, &buffer, high_accuracy, resolve_tailcalls)?; let rules_thread_handle = spawn(move || rules::RuleSet::new(&r)); - let rules = rules_thread_handle.join().unwrap()?; + let rules = match rules_thread_handle.join() { + Ok(Ok(rules)) => rules, + Ok(Err(_)) | Err(_) => return Err(Error::DescriptionEvaluationError), + }; // Fetch security checks let mut security_opts = security_checks_opts.unwrap_or_default(); @@ -655,15 +658,7 @@ fn find_file_capabilities<'a>( .extend(addresses.iter().cloned()); } - let mut matches: HashMap<&rules::Rule, Vec<(u64, (bool, Vec))>> = HashMap::new(); - for rule_set in [&ruleset.file_rules, &ruleset.function_rules].iter() { - for (rule, matched) in match_fn(rule_set, &file_features, &0, logger)?.1 { - matches - .entry(rule) - .or_default() - .extend(matched.iter().cloned()); - } - } + let (_, matches) = match_fn(&ruleset.file_rules, &file_features, &0x0, logger)?; if features_dump { map_features.extend(file_features.clone()); diff --git a/src/rules/features.rs b/src/rules/features.rs index 7aec92d..2d75e58 100755 --- a/src/rules/features.rs +++ b/src/rules/features.rs @@ -156,12 +156,12 @@ impl Feature { &value.get_bytes()?, description, )?)), - crate::rules::RuleFeatureType::Number(s) => Ok(Feature::Number(NumberFeature::new( + RuleFeatureType::Number(s) => Ok(Feature::Number(NumberFeature::new( s, &value.get_int()?, description, )?)), - crate::rules::RuleFeatureType::Offset(s) => Ok(Feature::Offset(OffsetFeature::new( + RuleFeatureType::Offset(s) => Ok(Feature::Offset(OffsetFeature::new( s, &value.get_int()?, description, @@ -171,7 +171,7 @@ impl Feature { description, )?)), RuleFeatureType::BasicBlock => Ok(Feature::BasicBlock(BasicBlockFeature::new()?)), - crate::rules::RuleFeatureType::Characteristic => Ok(Feature::Characteristic( + RuleFeatureType::Characteristic => Ok(Feature::Characteristic( CharacteristicFeature::new(&value.get_str()?, description)?, )), RuleFeatureType::Export => Ok(Feature::Export(ExportFeature::new( @@ -587,8 +587,8 @@ impl MnemonicFeature { } } -impl std::hash::Hash for MnemonicFeature { - fn hash(&self, state: &mut H) { +impl Hash for MnemonicFeature { + fn hash(&self, state: &mut H) { "mnemonic_feature".hash(state); self.value.hash(state); } @@ -634,8 +634,8 @@ impl OffsetFeature { } } -impl std::hash::Hash for OffsetFeature { - fn hash(&self, state: &mut H) { +impl Hash for OffsetFeature { + fn hash(&self, state: &mut H) { "offset_feature".hash(state); self.value.hash(state); } @@ -684,8 +684,8 @@ impl OperandOffsetFeature { } } -impl std::hash::Hash for OperandOffsetFeature { - fn hash(&self, state: &mut H) { +impl Hash for OperandOffsetFeature { + fn hash(&self, state: &mut H) { "operand_offset_feature".hash(state); self.index.hash(state); self.value.hash(state); @@ -739,8 +739,8 @@ impl NumberFeature { } } -impl std::hash::Hash for NumberFeature { - fn hash(&self, state: &mut H) { +impl Hash for NumberFeature { + fn hash(&self, state: &mut H) { "number_feature".hash(state); self.value.hash(state); } @@ -789,8 +789,8 @@ impl OperandNumberFeature { } } -impl std::hash::Hash for OperandNumberFeature { - fn hash(&self, state: &mut H) { +impl Hash for OperandNumberFeature { + fn hash(&self, state: &mut H) { "operand_number_feature".hash(state); self.index.hash(state); self.value.hash(state); @@ -1203,7 +1203,7 @@ impl RegexFeature { let rr = match fancy_regex::Regex::new(&rre) { Ok(s) => s, Err(e) => { - // println!("{:?}", e); + println!("{:?}", e); return Err(Error::FancyRegexError(Box::new(e))); } }; @@ -1246,8 +1246,8 @@ impl RegexFeature { } } -impl std::hash::Hash for RegexFeature { - fn hash(&self, state: &mut H) { +impl Hash for RegexFeature { + fn hash(&self, state: &mut H) { "regex_feature".hash(state); self.value.hash(state); } @@ -1336,8 +1336,8 @@ impl BytesFeature { } } -impl std::hash::Hash for BytesFeature { - fn hash(&self, state: &mut H) { +impl Hash for BytesFeature { + fn hash(&self, state: &mut H) { "bytes_feature".hash(state); self.value.hash(state); } @@ -1390,8 +1390,8 @@ impl ArchFeature { } } -impl std::hash::Hash for ArchFeature { - fn hash(&self, state: &mut H) { +impl Hash for ArchFeature { + fn hash(&self, state: &mut H) { "arch_feature".hash(state); self.value.to_lowercase().hash(state); } @@ -1440,8 +1440,8 @@ impl NamespaceFeature { } } -impl std::hash::Hash for NamespaceFeature { - fn hash(&self, state: &mut H) { +impl Hash for NamespaceFeature { + fn hash(&self, state: &mut H) { "namespace_feature".hash(state); self.value.to_lowercase().hash(state); } @@ -1490,8 +1490,8 @@ impl ClassFeature { } } -impl std::hash::Hash for ClassFeature { - fn hash(&self, state: &mut H) { +impl Hash for ClassFeature { + fn hash(&self, state: &mut H) { "class_feature".hash(state); self.value.to_lowercase().hash(state); } @@ -1544,8 +1544,8 @@ impl OsFeature { } } -impl std::hash::Hash for OsFeature { - fn hash(&self, state: &mut H) { +impl Hash for OsFeature { + fn hash(&self, state: &mut H) { "os_feature".hash(state); self.value.to_lowercase().hash(state); } @@ -1598,8 +1598,8 @@ impl FormatFeature { } } -impl std::hash::Hash for FormatFeature { - fn hash(&self, state: &mut H) { +impl Hash for FormatFeature { + fn hash(&self, state: &mut H) { "format_feature".hash(state); self.value.to_lowercase().hash(state); } diff --git a/src/rules/mod.rs b/src/rules/mod.rs index 80eba2f..dd96b79 100644 --- a/src/rules/mod.rs +++ b/src/rules/mod.rs @@ -19,6 +19,46 @@ fn translate_com_features(_name: &str, _com_type: &ComType) -> Vec Result { + match s { + "and" => Ok(CommandType::And), + "or" => Ok(CommandType::Or), + "not" => Ok(CommandType::Not), + "optional" => Ok(CommandType::Optional), + "process" => Ok(CommandType::Process), + "thread" => Ok(CommandType::Thread), + "call" => Ok(CommandType::Call), + "function" => Ok(CommandType::Function), + "basic block" => Ok(CommandType::BasicBlock), + "instruction" => Ok(CommandType::Instruction), + "description" => Ok(CommandType::Description), + s if s.ends_with(" or more") => Ok(CommandType::CountOrMore), + s if s.starts_with("count(") && s.ends_with(')') => Ok(CommandType::Count), + s if s.starts_with("com/") => Ok(CommandType::ComType), + _ => Ok(CommandType::Feature), + } + } +} #[derive(Debug)] pub enum Value { Str(String), @@ -313,8 +353,8 @@ impl Rule { } _ => { let v; // = ""; - //other features can have inline descriptions, like `number: 10 = CONST_FOO`. - //in this case, the RHS will be like `10 = CONST_FOO` or some other string + //other features can have inline descriptions, like `number: 10 = CONST_FOO`. + //in this case, the RHS will be like `10 = CONST_FOO` or some other string if s.contains(" = ") { if description.is_some() { // there is already a description passed in as a sub node, like: @@ -429,363 +469,266 @@ impl Rule { }) } + pub fn extract_elements_and_description( + vals: &[Yaml], + scopes: &Scopes, + ) -> Result<(Vec, String)> { + let mut description = String::new(); + + let params = vals.iter() + .map(|vv| Rule::build_statements(vv, scopes)) + .filter_map(|result| match result { + Ok(StatementElement::Description(s)) => { + description = s.value.clone(); + None + }, + Ok(elem) => Some(Ok(elem)), + Err(e) => Some(Err(e)) + }) + .collect::>>()?; + + Ok((params, description)) + } + + fn wrap_and_subscope( + scope: Scope, + params: Vec, + description: &str, + ) -> Result { + Ok(StatementElement::Statement(Box::new(Statement::Subscope( + SubscopeStatement::new( + scope, + StatementElement::Statement(Box::new(Statement::And( + AndStatement::new(params, description)?, + ))), + description, + )?, + )))) + } + pub fn build_statements(dd: &Yaml, scopes: &Scopes) -> Result { let d = dd .as_hash() .ok_or_else(|| Error::InvalidRule(line!(), "statement need to be hash".to_string()))?; if let Some((key, vval)) = d.into_iter().next() { - match key + let key_str = key .as_str() - .ok_or_else(|| Error::InvalidRule(line!(), format!("{:?}", key)))? - { - "description" => { + .ok_or_else(|| Error::InvalidRule(line!(), format!("{:?}", key)))?; + + let command_type = CommandType::from_str(key_str)?; + + match command_type { + CommandType::Description => { let val = vval .as_str() .ok_or_else(|| Error::InvalidRule(line!(), format!("{:?}", vval)))?; return Ok(StatementElement::Description(Box::new(Description::new( val, )?))); - } - "and" => { - let mut params = vec![]; - let mut description = "".to_string(); + }, + CommandType::And => { let val = vval .as_vec() .ok_or_else(|| Error::InvalidRule(line!(), format!("{:?}", vval)))?; - for vv in val { - let p = Rule::build_statements(vv, scopes)?; - match p { - StatementElement::Description(s) => description = s.value, - _ => params.push(p), - } - } + let (params, description) = Rule::extract_elements_and_description(val, scopes)?; return Ok(StatementElement::Statement(Box::new(Statement::And( AndStatement::new(params, &description)?, )))); - } - "or" => { - let mut params = vec![]; - let mut description = "".to_string(); + }, + CommandType::Or => { let val = vval .as_vec() .ok_or_else(|| Error::InvalidRule(line!(), format!("{:?}", vval)))?; - for vv in val { - let p = Rule::build_statements(vv, scopes)?; - match p { - StatementElement::Description(s) => description = s.value, - _ => params.push(p), - } - } + let (params, description) = Rule::extract_elements_and_description(val, scopes)?; return Ok(StatementElement::Statement(Box::new(Statement::Or( OrStatement::new(params, &description)?, )))); - } - "not" => { - let mut params = vec![]; - let mut description = "".to_string(); + }, + CommandType::Not => { let val = vval .as_vec() .ok_or_else(|| Error::InvalidRule(line!(), format!("{:?}", vval)))?; - for vv in val { - let p = Rule::build_statements(vv, scopes)?; - match p { - StatementElement::Description(s) => description = s.value, - _ => params.push(p), - } - } + let (params, description) = Rule::extract_elements_and_description(val, scopes)?; return Ok(StatementElement::Statement(Box::new(Statement::Not( NotStatement::new(params[0].clone(), &description)?, )))); - } - "optional" => { - let mut params = vec![]; - let mut description = "".to_string(); + }, + CommandType::Optional => { let val = vval .as_vec() .ok_or_else(|| Error::InvalidRule(line!(), format!("{:?}", vval)))?; - for vv in val { - let p = Rule::build_statements(vv, scopes)?; - match p { - StatementElement::Description(s) => description = s.value, - _ => params.push(p), - } - } + let (params, description) = Rule::extract_elements_and_description(val, scopes)?; return Ok(StatementElement::Statement(Box::new(Statement::Some( SomeStatement::new(0, params, &description)?, )))); - } - "process" => { + }, + CommandType::Process => { if [Scope::File].contains(&scopes.r#static.scope) || [Scope::File].contains(&scopes.dynamic.scope) { - let mut params = vec![]; - let mut description = "".to_string(); let val = vval .as_vec() .ok_or_else(|| Error::InvalidRule(line!(), format!("{:?}", vval)))?; - for vv in val { - let p = Rule::build_statements( - vv, - &Scopes { - r#static: StaticScope { - scope: Scope::Process, - }, - dynamic: DynamicScope { scope: Scope::None }, - }, - )?; - match p { - StatementElement::Description(s) => description = s.value, - _ => params.push(p), - } - } + + let process_scope = Scopes { + r#static: StaticScope { scope: Scope::Process }, + dynamic: DynamicScope { scope: Scope::None }, + }; + + let (params, description) = + Rule::extract_elements_and_description(val, &process_scope)?; + if params.len() != 1 { return Err(Error::InvalidRule( line!(), - format!("{:?}: {:?}", key, vval), + format!("process must have exactly one condition: {:?}", vval), )); } - return Ok(StatementElement::Statement(Box::new(Statement::Subscope( - SubscopeStatement::new( - Scope::Process, - params[0].clone(), - &description, - )?, - )))); + + return Rule::wrap_and_subscope(Scope::Process, params, &description); } + return Err(Error::InvalidRule( line!(), format!("{:?}: {:?}", key, vval), )); - } - "thread" => { + }, + CommandType::Thread => { if [Scope::File, Scope::Process].contains(&scopes.r#static.scope) || [Scope::File, Scope::Process].contains(&scopes.dynamic.scope) { - let mut params = vec![]; - let mut description = "".to_string(); + let thread_scope = Scopes { + r#static: StaticScope { scope: Scope::Thread }, + dynamic: DynamicScope { scope: Scope::None }, + }; + let val = vval .as_vec() .ok_or_else(|| Error::InvalidRule(line!(), format!("{:?}", vval)))?; - for vv in val { - let p = Rule::build_statements( - vv, - &Scopes { - r#static: StaticScope { - scope: Scope::Thread, - }, - dynamic: DynamicScope { scope: Scope::None }, - }, - )?; - match p { - StatementElement::Description(s) => description = s.value, - _ => params.push(p), - } - } + + let (params, description) = Rule::extract_elements_and_description(val, &thread_scope)?; + if params.len() != 1 { return Err(Error::InvalidRule( line!(), - format!("{:?}: {:?}", key, vval), + format!("process must have exactly one condition: {:?}", vval), )); } - return Ok(StatementElement::Statement(Box::new(Statement::Subscope( - SubscopeStatement::new(Scope::Thread, params[0].clone(), &description)?, - )))); + return Rule::wrap_and_subscope(Scope::Thread, params, &description); } + return Err(Error::InvalidRule( line!(), format!("{:?}: {:?}", key, vval), )); - } - "call" => { - let mut params = vec![]; - let mut description = "".to_string(); - - match vval { - Yaml::Array(arr) => { - for vv in arr { - let p = Rule::build_statements( - vv, - &Scopes { - r#static: StaticScope { scope: Scope::Call }, - dynamic: DynamicScope { scope: Scope::SpanOfCalls }, - }, - )?; - match p { - StatementElement::Description(s) => description = s.value, - _ => params.push(p), - } - } - } - Yaml::Hash(_) => { - let p = Rule::build_statements( - vval, - &Scopes { - r#static: StaticScope { scope: Scope::Call }, - dynamic: DynamicScope { scope: Scope::SpanOfCalls }, - }, - )?; - params.push(p); + }, + CommandType::Call => { + let call_scope = Scopes { + r#static: StaticScope { scope: Scope::Call }, + dynamic: DynamicScope { scope: Scope::SpanOfCalls }, + }; + + let val_list = match vval { + Yaml::Array(arr) => arr.as_slice(), + Yaml::Hash(_) => std::slice::from_ref(vval), + _ => { + return Err(Error::InvalidRule( + line!(), + format!("call expects array or hash: {:?}", vval), + )) } - _ => return Err(Error::InvalidRule(line!(), format!("call expects array or hash: {:?}", vval))), - } + }; - if params.is_empty() { - return Err(Error::InvalidRule(line!(), format!("call must have at least one condition: {:?}", vval))); - } + let (params, description) = Rule::extract_elements_and_description(val_list, &call_scope)?; - return Ok(StatementElement::Statement(Box::new(Statement::Subscope( - SubscopeStatement::new( - Scope::Call, - StatementElement::Statement(Box::new(Statement::And(AndStatement::new(params, &description)?))), - &description - )?, - )))); - } + if params.len() != 1 { + return Err(Error::InvalidRule( + line!(), + format!("process must have exactly one condition: {:?}", vval), + )); + } - "function" => { + return Rule::wrap_and_subscope(Scope::Call, params, &description); + }, + CommandType::Function => { if Scope::File == scopes.r#static.scope || Scope::File == scopes.dynamic.scope { - let mut params = vec![]; - let mut description = "".to_string(); + let function_scope = Scopes { + r#static: StaticScope { scope: Scope::Function }, + dynamic: DynamicScope { scope: Scope::None }, + }; + let val = vval .as_vec() .ok_or_else(|| Error::InvalidRule(line!(), format!("{:?}", vval)))?; - for vv in val { - let p = Rule::build_statements( - vv, - &Scopes { - r#static: StaticScope { - scope: Scope::Function, - }, - dynamic: DynamicScope { scope: Scope::None }, - }, - )?; - match p { - StatementElement::Description(s) => description = s.value, - _ => params.push(p), - } - } + + let (params, description) = Rule::extract_elements_and_description(val, &function_scope)?; + if params.len() != 1 { - return Err(Error::InvalidRule( - line!(), - format!("{:?}: {:?}", key, vval), - )); + return Err(Error::InvalidRule(line!(), format!("{:?}: {:?}", key, vval))); } + return Ok(StatementElement::Statement(Box::new(Statement::Subscope( - SubscopeStatement::new( - Scope::Function, - params[0].clone(), - &description, - )?, + SubscopeStatement::new(Scope::Function, params[0].clone(), &description)?, )))); } - return Err(Error::InvalidRule( - line!(), - format!("{:?}: {:?}", key, vval), - )); - } - "basic block" => { + + return Err(Error::InvalidRule(line!(), format!("{:?}: {:?}", key, vval))); + }, + CommandType::BasicBlock => { if [Scope::Function, Scope::BasicBlock].contains(&scopes.r#static.scope) || [Scope::Function, Scope::BasicBlock].contains(&scopes.dynamic.scope) { - let mut params = vec![]; - let mut description = "".to_string(); + let bb_scope = Scopes { + r#static: StaticScope { scope: Scope::BasicBlock }, + dynamic: DynamicScope { scope: Scope::None }, + }; - match vval { - Yaml::Array(arr) => { - for vv in arr { - let p = Rule::build_statements( - vv, - &Scopes { - r#static: StaticScope { scope: Scope::BasicBlock }, - dynamic: DynamicScope { scope: Scope::None }, - }, - )?; - match p { - StatementElement::Description(s) => description = s.value, - _ => params.push(p), - } - } - } - Yaml::Hash(_) => { - let p = Rule::build_statements( - vval, - &Scopes { - r#static: StaticScope { scope: Scope::BasicBlock }, - dynamic: DynamicScope { scope: Scope::None }, - }, - )?; - params.push(p); + let val_list = match vval { + Yaml::Array(arr) => arr.as_slice(), + Yaml::Hash(_) => std::slice::from_ref(vval), + _ => { + return Err(Error::InvalidRule( + line!(), + format!("basic block expects array or hash: {:?}", vval), + )) } - _ => return Err(Error::InvalidRule(line!(), format!("basic block expects array or hash: {:?}", vval))), - } + }; + + let (params, description) = Rule::extract_elements_and_description(val_list, &bb_scope)?; if params.is_empty() { return Err(Error::InvalidRule(line!(), format!("basic block must have at least one condition: {:?}", vval))); } - return Ok(StatementElement::Statement(Box::new(Statement::Subscope( - SubscopeStatement::new( - Scope::BasicBlock, - StatementElement::Statement(Box::new(Statement::And(AndStatement::new(params, &description)?))), - &description - )?, - )))); + return Rule::wrap_and_subscope(Scope::BasicBlock, params, &description); } - return Err(Error::InvalidRule( - line!(), - format!("{:?}: {:?}", key, vval), - )); - } - "instruction" => { + + return Err(Error::InvalidRule(line!(), format!("{:?}: {:?}", key, vval))); + }, + CommandType::Instruction => { if [Scope::BasicBlock, Scope::Function].contains(&scopes.r#static.scope) || [Scope::BasicBlock, Scope::Function].contains(&scopes.dynamic.scope) { - let mut params = vec![]; - let mut description = "".to_string(); + let instruction_scope = Scopes { + r#static: StaticScope { scope: Scope::Instruction }, + dynamic: DynamicScope { scope: Scope::None }, + }; + let val = vval .as_vec() .ok_or_else(|| Error::InvalidRule(line!(), format!("{:?}", vval)))?; - for vv in val { - let p = Rule::build_statements(vv, scopes)?; - match p { - StatementElement::Description(s) => description = s.value, - _ => params.push(p), - } - } - // Special case: if there's only one parameter, create subscope with just that parameter - if params.len() == 1 { - return Ok(StatementElement::Statement(Box::new(Statement::Subscope( - SubscopeStatement::new( - Scope::Instruction, - params[0].clone(), - &description, - )?, - )))); - } - - // For multiple parameters, combine them with AND logic first - let params_and = StatementElement::Statement(Box::new(Statement::And( - AndStatement::new(params, &description)?, - ))); + let (params, description) = Rule::extract_elements_and_description(val, &instruction_scope)?; - // Wrap the AND statement in an instruction subscope - return Ok(StatementElement::Statement(Box::new(Statement::Subscope( - SubscopeStatement::new( - Scope::Instruction, - params_and, - &description, - )?, - )))); + return Rule::wrap_and_subscope(Scope::Instruction, params, &description); } - // Return error if scope is invalid for instruction statements return Err(Error::InvalidRule( line!(), format!("{:?}, {:?}: {:?}", scopes, key, vval), )); - } + }, _ => { let kkey = key.as_str().ok_or_else(|| { Error::InvalidRule(line!(), format!("{:?} must be string", key)) @@ -1019,6 +962,7 @@ pub fn get_rules(rule_path: &str) -> Result> { Ok(rules) } + #[derive(Debug)] pub struct RuleSet { pub rules: Vec, @@ -1042,6 +986,10 @@ impl RuleSet { } } +pub fn get_instruction_rules(rules: &[Rule]) -> Result> { + get_rules_for_scope(rules, &Scope::Instruction) +} + pub fn get_basic_block_rules(rules: &[Rule]) -> Result> { get_rules_for_scope(rules, &Scope::BasicBlock) } @@ -1092,7 +1040,18 @@ pub fn get_rules_and_dependencies<'a>(rules: &'a [Rule], rule_name: &str) -> Res ) -> Result<()> { want.push(rule.name.clone()); for dep in rule.get_dependencies(namespaces)? { - rec(want, rules_by_name[&dep], rules_by_name, namespaces)?; + match rules_by_name.get(&dep) { + Some(dep_rule) => { + rec(want, dep_rule, rules_by_name, namespaces)?; + } + None => { + eprintln!("Rule not found: {}", dep); + return Err(Error::MatchRuleNotFound(format!( + "Rule '{}' not found in the rules set", + dep + ))); + } + } } Ok(()) } @@ -1103,11 +1062,13 @@ pub fn get_rules_and_dependencies<'a>(rules: &'a [Rule], rule_name: &str) -> Res &rules_by_name, &namespaces, )?; + for (_, rule) in rules_by_name { if wanted.contains(&rule.name) { res.push(rule) } } + Ok(res) } @@ -1121,88 +1082,45 @@ pub fn get_rules_with_scope<'a>(rules: Vec<&'a Rule>, scope: &Scope) -> Result Vec { + namespace.split('/') + .scan(String::new(), |state, part| { + if !state.is_empty() { + state.push('/'); + } + state.push_str(part); + Some(state.clone()) + }) + .collect() +} + pub fn index_rules_by_namespace(rules: &[Rule]) -> Result>> { let mut namespaces: HashMap> = HashMap::new(); + for rule in rules { - if rule - .meta - .contains_key(&yaml_rust::Yaml::String("namespace".to_string())) - { - match &rule.meta[&yaml_rust::Yaml::String("namespace".to_string())] { - yaml_rust::Yaml::String(namespace) => { - let mut ns = Some(namespace.clone()); - while let Some(nss) = ns { - match namespaces.get_mut(&nss) { - Some(n) => { - n.push(rule); - } - _ => { - namespaces.insert(nss.clone(), vec![rule]); - } - } - let parts: Vec<&str> = nss.split('/').collect(); - if parts.len() == 1 { - ns = None; - } else { - let mut nsss = "".to_string(); - for item in parts.iter().take(parts.len() - 1) { - nsss += "/"; - nsss += item; - } - ns = Some(nsss[1..].to_string()); - } - } - } - _ => { - continue; - } + if let Some(Yaml::String(namespace)) = rule.meta.get(&Yaml::String("namespace".to_string())) { + for path in generate_namespace_paths(namespace) { + namespaces.entry(path).or_insert_with(Vec::new).push(rule); } } } + Ok(namespaces) } pub fn index_rules_by_namespace2<'a>(rules: &[&'a Rule]) -> Result>> { - let mut namespaces: HashMap> = HashMap::new(); - for rule in rules { - if rule - .meta - .contains_key(&yaml_rust::Yaml::String("namespace".to_string())) - { - match &rule.meta[&yaml_rust::Yaml::String("namespace".to_string())] { - yaml_rust::Yaml::String(namespace) => { - let mut ns = Some(namespace.clone()); - while let Some(nss) = ns { - match namespaces.get_mut(&nss) { - Some(n) => { - n.push(rule); - } - _ => { - namespaces.insert(nss.clone(), vec![rule]); - } - } - let parts: Vec<&str> = nss.split('/').collect(); - if parts.len() == 1 { - ns = None; - } else { - let mut nsss = "".to_string(); - for item in parts.iter().take(parts.len() - 1) { - nsss += "/"; - nsss += item; - } - ns = Some(nsss[1..].to_string()); - } - } - } - _ => { - continue; - } + let mut namespaces: HashMap> = HashMap::new(); + + for &rule in rules { + if let Some(Yaml::String(namespace)) = rule.meta.get(&Yaml::String("namespace".to_string())) { + for path in generate_namespace_paths(namespace) { + namespaces.entry(path).or_insert_with(Vec::new).push(rule); } } } + Ok(namespaces) } - pub fn topologically_order_rules(rules: Vec<&Rule>) -> Result> { //# we evaluate `rules` multiple times, so if its a generator, realize it into a list. let namespaces = index_rules_by_namespace2(&rules)?; diff --git a/src/rules/statement.rs b/src/rules/statement.rs index 5b97885..7015af3 100644 --- a/src/rules/statement.rs +++ b/src/rules/statement.rs @@ -1,4 +1,6 @@ +use std::collections::{HashMap, HashSet}; use crate::{rules::features::Feature, Error, Result}; +use crate::rules::Scope; #[derive(Debug, Clone, Hash, PartialEq, Eq)] pub enum StatementElement { @@ -10,7 +12,7 @@ pub enum StatementElement { impl StatementElement { pub fn evaluate( &self, - features: &std::collections::HashMap>, + features: &HashMap>, ) -> Result<(bool, Vec)> { match self { StatementElement::Statement(s) => s.evaluate(features), @@ -43,7 +45,7 @@ impl Statement { } pub fn evaluate( &self, - features: &std::collections::HashMap>, + features: &HashMap>, ) -> Result<(bool, Vec)> { match self { Statement::And(s) => s.evaluate(features), @@ -78,7 +80,7 @@ impl AndStatement { } pub fn evaluate( &self, - features: &std::collections::HashMap>, + features: &HashMap>, ) -> Result<(bool, Vec)> { let mut res = true; for child in &self.children { @@ -110,7 +112,7 @@ impl OrStatement { } pub fn evaluate( &self, - features: &std::collections::HashMap>, + features: &HashMap>, ) -> Result<(bool, Vec)> { let mut res = false; for child in &self.children { @@ -138,7 +140,7 @@ impl NotStatement { } pub fn evaluate( &self, - features: &std::collections::HashMap>, + features: &HashMap>, ) -> Result<(bool, Vec)> { Ok((!self.child.evaluate(features)?.0, vec![])) } @@ -172,7 +174,7 @@ impl SomeStatement { } pub fn evaluate( &self, - features: &std::collections::HashMap>, + features: &HashMap>, ) -> Result<(bool, Vec)> { let mut res = 0; for child in &self.children { @@ -211,7 +213,7 @@ impl RangeStatement { } pub fn evaluate( &self, - features: &std::collections::HashMap>, + features: &HashMap>, ) -> Result<(bool, Vec)> { if let StatementElement::Feature(f) = &self.child { let count = match features.get(f) { @@ -227,16 +229,57 @@ impl RangeStatement { } } +#[derive(Debug, Clone, Hash, PartialEq, Eq)] +pub struct SubscopeInstructionEvaluator; + +impl SubscopeInstructionEvaluator { + pub fn evaluate( + statement: &StatementElement, + features: &HashMap>, + ) -> Result<(bool, Vec)> { + let mut addr_to_features: HashMap> = HashMap::new(); + + for (feature, addrs) in features.iter() { + for addr in addrs { + addr_to_features.entry(*addr).or_default().insert(feature); + } + } + + if let StatementElement::Statement(stmt) = statement { + if let Statement::And(and_stmt) = stmt.as_ref() { + for (_addr, feature_set) in addr_to_features.iter() { + let mut matched = true; + for elem in &and_stmt.children { + if let StatementElement::Feature(f) = elem { + if !feature_set.iter().any(|feat| *feat == f.as_ref()) { + matched = false; + break; + } + } else { + matched = false; + break; + } + } + if matched { + return Ok((true, vec![])); + } + } + } + } + + Ok((false, vec![])) + } +} #[derive(Debug, Clone, Hash, PartialEq, Eq)] pub struct SubscopeStatement { child: StatementElement, - scope: crate::rules::Scope, + scope: Scope, description: String, } impl SubscopeStatement { pub fn new( - scope: crate::rules::Scope, + scope: Scope, params: StatementElement, description: &str, ) -> Result { @@ -251,11 +294,16 @@ impl SubscopeStatement { } pub fn evaluate( &self, - features: &std::collections::HashMap>, + features: &HashMap>, ) -> Result<(bool, Vec)> { - self.child.evaluate(features) - // Err(Error::SubscopeEvaluationError) + match self.scope { + Scope::Instruction => { + SubscopeInstructionEvaluator::evaluate(&self.child, features) + } + _ => self.child.evaluate(features), + } } + } #[derive(Debug, Clone, Hash, PartialEq, Eq)] @@ -271,7 +319,7 @@ impl Description { } pub fn evaluate( &self, - _features: &std::collections::HashMap>, + _features: &HashMap>, ) -> Result<(bool, Vec)> { Err(Error::DescriptionEvaluationError) }