use super::evaluate_rules;
use crate::LibmagicError;
use crate::evaluator::{EvaluationContext, RecursionGuard, RuleMatch, offset};
use crate::parser::ast::MagicRule;
use log::{debug, warn};
use std::sync::atomic::{AtomicBool, Ordering};
struct SubroutineScope<'a> {
context: &'a mut EvaluationContext,
saved_anchor: usize,
saved_base: usize,
saved_flip: bool,
}
impl<'a> SubroutineScope<'a> {
fn enter(context: &'a mut EvaluationContext, use_site: usize, flip_use: bool) -> Self {
let saved_anchor = context.last_match_end();
let saved_base = context.base_offset();
let saved_flip = context.flip_endian();
context.set_last_match_end(use_site);
context.set_base_offset(use_site);
context.set_flip_endian(saved_flip ^ flip_use);
Self {
context,
saved_anchor,
saved_base,
saved_flip,
}
}
fn context(&mut self) -> &mut EvaluationContext {
self.context
}
}
impl Drop for SubroutineScope<'_> {
fn drop(&mut self) {
self.context.set_last_match_end(self.saved_anchor);
self.context.set_base_offset(self.saved_base);
self.context.set_flip_endian(self.saved_flip);
}
}
static USE_WITHOUT_RULE_ENV_WARNED: AtomicBool = AtomicBool::new(false);
pub(crate) fn evaluate_use_rule(
rule: &MagicRule,
name: &str,
flip_endian: bool,
buffer: &[u8],
context: &mut EvaluationContext,
) -> Result<(Option<usize>, Vec<RuleMatch>), LibmagicError> {
let Some(env) = context.rule_env() else {
if USE_WITHOUT_RULE_ENV_WARNED.swap(true, Ordering::Relaxed) {
debug!("use directive '{name}' evaluated without a rule environment; no-op");
} else {
warn!(
"use directive '{name}' evaluated without a rule environment; treating as no-op (subsequent occurrences suppressed)"
);
}
return Ok((None, Vec::new()));
};
let Some(subroutine_rules) = env.name_table.get(name) else {
warn!("use directive references unknown name '{name}'");
return Ok((None, Vec::new()));
};
let name_message = env.name_table.name_message(name);
let absolute_offset = offset::resolve_offset_with_base(
&rule.offset,
buffer,
context.last_match_end(),
context.base_offset(),
)?;
let (subroutine_matches, terminal_anchor) = {
let mut scope = SubroutineScope::enter(context, absolute_offset, flip_endian);
let mut guard = RecursionGuard::enter(scope.context())?;
let matches = evaluate_rules(&subroutine_rules, buffer, guard.context())?;
let terminal = guard.context().last_match_end();
(matches, terminal)
};
let matches = match name_message {
Some(msg) if !msg.is_empty() => {
let attached = if crate::evaluator::strip_no_separator_marker(&msg).is_some() {
msg
} else {
format!("\\b{msg}")
};
let name_match = RuleMatch::new(
attached,
absolute_offset,
rule.level,
crate::parser::ast::Value::Uint(0),
rule.typ.clone(),
RuleMatch::calculate_confidence(rule.level),
);
let mut combined = Vec::with_capacity(subroutine_matches.len() + 1);
combined.push(name_match);
combined.extend(subroutine_matches);
combined
}
_ => subroutine_matches,
};
Ok((Some(terminal_anchor), matches))
}