Skip to content

Commit

Permalink
rust: transformation module todo fixed
Browse files Browse the repository at this point in the history
  • Loading branch information
rizsotto committed Jan 18, 2025
1 parent 75c6cb7 commit 691bf14
Showing 1 changed file with 204 additions and 57 deletions.
261 changes: 204 additions & 57 deletions rust/bear/src/semantic/transformation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,81 +6,103 @@
//! It can also alter the compiler flags of the compiler calls. The actions
//! are defined in the configuration this module is given.
use crate::config;
use crate::semantic;
use crate::semantic::Transform;
use crate::{config, semantic};

pub enum Transformation {
None,
Config(Vec<config::Compiler>),
use std::collections::HashMap;
use std::path::PathBuf;

pub struct Transformation {
compilers: HashMap<PathBuf, Vec<config::Compiler>>,
}

impl From<&config::Output> for Transformation {
fn from(config: &config::Output) -> Self {
match config {
config::Output::Clang { compilers, .. } => {
if compilers.is_empty() {
Transformation::None
} else {
let compilers = compilers.clone();
Transformation::Config(compilers)
}
}
config::Output::Semantic { .. } => Transformation::None,
config::Output::Clang { compilers, .. } => compilers.as_slice().into(),
config::Output::Semantic { .. } => Transformation::new(),
}
}
}

impl From<&[config::Compiler]> for Transformation {
fn from(config: &[config::Compiler]) -> Self {
let mut compilers = HashMap::new();
for compiler in config {
compilers
.entry(compiler.path.clone())
.or_insert_with(Vec::new)
.push(compiler.clone());
}
Transformation { compilers }
}
}

impl Transform for Transformation {
impl semantic::Transform for Transformation {
fn apply(&self, input: semantic::CompilerCall) -> Option<semantic::CompilerCall> {
let semantic::CompilerCall {
compiler,
passes,
working_dir,
} = &input;
match self.lookup(compiler) {
Some(config::Compiler {
ignore: config::IgnoreOrConsider::Always,
..
}) => None,
Some(config::Compiler {
ignore: config::IgnoreOrConsider::Conditional,
arguments,
..
}) => {
if Self::filter(arguments, passes) {
None
} else {
Some(input)
}
}
Some(config::Compiler {
ignore: config::IgnoreOrConsider::Never,
arguments,
..
}) => {
let new_passes = Transformation::execute(arguments, passes);
Some(semantic::CompilerCall {
compiler: compiler.clone(),
working_dir: working_dir.clone(),
passes: new_passes,
})
}
None => Some(input),
if let Some(configs) = self.compilers.get(&input.compiler) {
Self::apply_when_not_empty(configs.as_slice(), input)
} else {
Some(input)
}
}
}

impl Transformation {
// TODO: allow multiple matches for the same compiler
fn lookup(&self, compiler: &std::path::Path) -> Option<&config::Compiler> {
match self {
Transformation::Config(compilers) => compilers.iter().find(|c| c.path == compiler),
_ => None,
fn new() -> Self {
Transformation {
compilers: HashMap::new(),
}
}

/// Apply the transformation to the compiler call.
///
/// Multiple configurations can be applied to the same compiler call.
/// And depending on the instruction from the configuration, the compiler call
/// can be ignored, modified, or left unchanged. The conditional ignore will
/// check if the compiler call matches the flags defined in the configuration.
fn apply_when_not_empty(
configs: &[config::Compiler],
input: semantic::CompilerCall,
) -> Option<semantic::CompilerCall> {
let mut current_input = Some(input);

for config in configs {
current_input = match config {
config::Compiler {
ignore: config::IgnoreOrConsider::Always,
..
} => None,
config::Compiler {
ignore: config::IgnoreOrConsider::Conditional,
arguments,
..
} => current_input.filter(|input| !Self::match_condition(arguments, &input.passes)),
config::Compiler {
ignore: config::IgnoreOrConsider::Never,
arguments,
..
} => current_input.map(|input| semantic::CompilerCall {
compiler: input.compiler.clone(),
working_dir: input.working_dir.clone(),
passes: Transformation::apply_argument_changes(
arguments,
input.passes.as_slice(),
),
}),
};

if current_input.is_none() {
break;
}
}
current_input
}

fn filter(arguments: &config::Arguments, passes: &[semantic::CompilerPass]) -> bool {
/// Check if the compiler call matches the condition defined in the configuration.
///
/// Any compiler pass that matches the flags defined in the configuration will cause
/// the whole compiler call to be ignored.
fn match_condition(arguments: &config::Arguments, passes: &[semantic::CompilerPass]) -> bool {
let match_flags = arguments.match_.as_slice();
passes.iter().any(|pass| match pass {
semantic::CompilerPass::Compile { flags, .. } => {
Expand All @@ -90,7 +112,11 @@ impl Transformation {
})
}

fn execute(
/// Apply the changes defined in the configuration to the compiler call.
///
/// The changes can be to remove or add flags to the compiler call.
/// Only the flags will be changed, but applies to all compiler passes.
fn apply_argument_changes(
arguments: &config::Arguments,
passes: &[semantic::CompilerPass],
) -> Vec<semantic::CompilerPass> {
Expand Down Expand Up @@ -122,3 +148,124 @@ impl Transformation {
new_passes
}
}

#[cfg(test)]
mod tests {
use super::*;
use crate::config::{Arguments, Compiler, IgnoreOrConsider};
use crate::semantic::{CompilerCall, CompilerPass, Transform};
use std::path::PathBuf;

#[test]
fn test_apply_no_filter() {
let input = CompilerCall {
compiler: std::path::PathBuf::from("gcc"),
passes: vec![CompilerPass::Compile {
source: PathBuf::from("main.c"),
output: PathBuf::from("main.o").into(),
flags: vec!["-O2".into()],
}],
working_dir: std::path::PathBuf::from("/project"),
};

let sut = Transformation::from(&config::Output::Semantic {});
let result = sut.apply(input);

let expected = CompilerCall {
compiler: std::path::PathBuf::from("gcc"),
passes: vec![CompilerPass::Compile {
source: PathBuf::from("main.c"),
output: PathBuf::from("main.o").into(),
flags: vec!["-O2".into()],
}],
working_dir: std::path::PathBuf::from("/project"),
};
assert_eq!(result, Some(expected));
}

#[test]
fn test_apply_filter_match() {
let input = CompilerCall {
compiler: std::path::PathBuf::from("cc"),
passes: vec![CompilerPass::Compile {
source: PathBuf::from("main.c"),
output: PathBuf::from("main.o").into(),
flags: vec!["-O2".into()],
}],
working_dir: std::path::PathBuf::from("/project"),
};

let sut: Transformation = vec![Compiler {
path: std::path::PathBuf::from("cc"),
ignore: IgnoreOrConsider::Always,
arguments: Arguments::default(),
}]
.as_slice()
.into();
let result = sut.apply(input);
assert!(result.is_none());
}

#[test]
fn test_apply_conditional_match() {
let input = CompilerCall {
compiler: std::path::PathBuf::from("gcc"),
passes: vec![CompilerPass::Compile {
source: PathBuf::from("main.c"),
output: PathBuf::from("main.o").into(),
flags: vec!["-O2".into(), "-Wall".into()],
}],
working_dir: std::path::PathBuf::from("/project"),
};

let sut: Transformation = vec![Compiler {
path: std::path::PathBuf::from("gcc"),
ignore: IgnoreOrConsider::Conditional,
arguments: Arguments {
match_: vec!["-O2".into()],
..Arguments::default()
},
}]
.as_slice()
.into();
let result = sut.apply(input);
assert!(result.is_none());
}

#[test]
fn test_apply_ignore_never_modify_arguments() {
let input = CompilerCall {
compiler: std::path::PathBuf::from("gcc"),
passes: vec![CompilerPass::Compile {
source: PathBuf::from("main.c"),
output: PathBuf::from("main.o").into(),
flags: vec!["-O2".into()],
}],
working_dir: std::path::PathBuf::from("/project"),
};

let sut: Transformation = vec![Compiler {
path: std::path::PathBuf::from("gcc"),
ignore: IgnoreOrConsider::Never,
arguments: Arguments {
add: vec!["-Wall".into()],
remove: vec!["-O2".into()],
..Arguments::default()
},
}]
.as_slice()
.into();
let result = sut.apply(input);

let expected = CompilerCall {
compiler: std::path::PathBuf::from("gcc"),
passes: vec![CompilerPass::Compile {
source: PathBuf::from("main.c"),
output: PathBuf::from("main.o").into(),
flags: vec!["-Wall".into()],
}],
working_dir: std::path::PathBuf::from("/project"),
};
assert_eq!(result, Some(expected));
}
}

0 comments on commit 691bf14

Please sign in to comment.