blob: 29213ae28e005da18caa06306e4d208efce73c6a [file] [log] [blame]
// Copyright 2019 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
use {crate::selectors, fidl_fuchsia_diagnostics::StringSelector, std::collections::HashSet};
/// Struct that encodes the information needed to
/// represent a component selector as a DFA. This DFA representation
/// is used to evaluate a component_hierarchy path against a selector
/// to determine if the data under the path is being selected for.
// TODO(fxbug.dev/47015): The component DFA is going to be unusued until we implement the custom
// DFA traversal for selection of specific properties on a hierarchy.
#[allow(dead_code)]
pub(crate) struct SelectorAutomata<'a> {
// The individual states that make up
// the selector state-machine.
states: &'a Vec<StringSelector>,
// The set of states that the automata is currently
// validly at, based on its evaluatation of some input.
state_indices: Option<HashSet<usize>>,
}
// TODO(fxbug.dev/47015): The component DFA is going to be unusued until we implement the custom
// DFA traversal for selection of specific properties on a hierarchy.
#[allow(dead_code)]
impl<'a> SelectorAutomata<'a> {
pub fn new(states: &'a Vec<StringSelector>) -> Self {
SelectorAutomata { states: states, state_indices: None }
}
/// Given a tokenized realmpath through the hub
/// to a target component, evaluates a SelectorAutomata
/// representing a component selector to see if the
/// tokenized realm path is selected for by the automata.
pub fn evaluate_automata_against_path(&mut self, hierarchy_path: &Vec<String>) -> bool {
debug_assert!(&self.state_indices.is_none());
// Initialize the DFA to be staged for execution starting
// at the entry state. Note that we start at the end of the selector
// and work backwards, since the most common usecase will have specified
// target components that can be quickly filtered out.
let entry_state_index = self.states.len() - 1;
let mut curr_state_set = HashSet::new();
curr_state_set.insert(entry_state_index);
self.state_indices = Some(curr_state_set);
for (path_index, path_value) in hierarchy_path.iter().enumerate().rev() {
// We have to do extra state analysis on the final path value, to make
// sure that there are not only stateful matches, but that atleast one
// of those stateful matches is an exit state in the DFA.
if path_index > 0 {
let next_generation_states = match &self.state_indices {
Some(state_indices) => {
evaluate_single_generation(path_value, state_indices, self.states)
}
None => unreachable!(
"state indices should always be initialized at start of execution."
),
};
if next_generation_states.is_empty() {
return false;
} else {
self.state_indices = Some(next_generation_states);
}
} else {
let final_generation_states = match &self.state_indices {
Some(state_indices) => state_indices,
None => unreachable!(
"even in the final generation, the state indices should not be None."
),
};
// If we're at the final path value and not sitting at the exit
// node, there's no way for us to match.
if !final_generation_states.contains(&0) {
return false;
}
// If we're at the final path value and the exit node is present, the only
// match result we care about is the result of the exit node.
if !evaluate_path_state_with_selector_node(path_value, &self.states[0]) {
return false;
}
return true;
}
}
unreachable!("There is no way to reach this point.");
}
}
// Checks to see whether a given tokenized value
// in the hub path can be selected for by a given
// StringSelector.
fn evaluate_path_state_with_selector_node(
path_state: &str,
selector_node: &StringSelector,
) -> bool {
match selector_node {
// TODO(fxbug.dev/4601): String patterns that contain wildcards must be pattern matched on.
// Can we convert these to regex to avoid another custom state machine?
StringSelector::StringPattern(string_pattern) => {
if string_pattern == selectors::WILDCARD_SYMBOL_STR {
return true;
} else {
// TODO(fxbug.dev/4601): Support wildcarded string_literals.
return string_pattern == path_state;
}
}
StringSelector::ExactMatch(exact_match) => exact_match == path_state,
_ => false,
}
}
// Evaluate a single generation of a given
// automata, and return the states of the next generation.
fn evaluate_single_generation(
path_value: &String,
state_indices: &HashSet<usize>,
states: &Vec<StringSelector>,
) -> HashSet<usize> {
let mut next_generation_state_indices: HashSet<usize> = HashSet::new();
for state_index in state_indices {
let state_node = &states[*state_index as usize];
if evaluate_path_state_with_selector_node(&path_value, state_node) {
// NOTE: If we reintroduce globs, at this point, glob states must
// be re-added to the next_generation_state_indices since globs
// can repeat.
// Always provide the next state in the automata if the current
// state was a match, assuming it's not the exit state that was
// arrived at early.
if *state_index > 0 {
next_generation_state_indices.insert(state_index - 1);
}
}
}
next_generation_state_indices
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn canonical_automata_simulator_test() {
let test_vector: Vec<(Vec<String>, Vec<StringSelector>, bool)> = vec![
(
vec!["a".to_string(), "b".to_string(), "c".to_string()],
vec![
StringSelector::StringPattern("a".to_string()),
StringSelector::StringPattern("b".to_string()),
StringSelector::StringPattern("c".to_string()),
],
true,
),
(
vec!["a".to_string(), "b".to_string(), "c".to_string()],
vec![
StringSelector::StringPattern("*".to_string()),
StringSelector::StringPattern("b".to_string()),
StringSelector::StringPattern("c".to_string()),
],
true,
),
(
vec!["a".to_string(), "b".to_string(), "c".to_string()],
vec![
StringSelector::StringPattern("a".to_string()),
StringSelector::StringPattern("*".to_string()),
StringSelector::StringPattern("*".to_string()),
],
true,
),
(
vec!["a".to_string(), "b".to_string(), "c".to_string()],
vec![
StringSelector::StringPattern("b".to_string()),
StringSelector::StringPattern("*".to_string()),
StringSelector::StringPattern("*".to_string()),
],
false,
),
// TODO(fxbug.dev/4601): This should pass once wildcarded string literals
// are supported.
(
vec!["a".to_string(), "bob".to_string(), "c".to_string()],
vec![
StringSelector::StringPattern("a".to_string()),
StringSelector::StringPattern("b*".to_string()),
StringSelector::StringPattern("c".to_string()),
],
false,
),
];
for (test_path, component_node_vec, result) in test_vector {
let mut automata = SelectorAutomata::new(&component_node_vec);
assert_eq!(result, automata.evaluate_automata_against_path(&test_path));
}
}
}