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 11, 2025
1 parent cbeabc8 commit 4624020
Showing 1 changed file with 198 additions and 54 deletions.
252 changes: 198 additions & 54 deletions rust/bear/src/semantic/transformation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,79 +8,98 @@
use crate::config;
use crate::semantic;
use crate::semantic::Transform;

pub enum Transformation {
None,
Config(Vec<config::Compiler>),
pub struct Transformation {
compilers: 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, .. } => Transformation {
compilers: compilers.clone(),
},
config::Output::Semantic { .. } => Transformation { compilers: vec![] },
}
}
}

impl Transform for Transformation {
impl semantic::Transform for Transformation {
/// Apply the transformation to the compiler call.
///
/// Optimize for the case when the configuration is empty.
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,
})
if self.compilers.is_empty() {
Some(input)
} else {
// Find the configurations that match the compiler name.
let compiler = &input.compiler.clone();
let configs: Vec<_> = self
.compilers
.iter()
.filter(|config| config.path == *compiler)
.collect();

// Apply the transformation if there are any configurations.
if configs.is_empty() {
Some(input)
} else {
Self::apply_when_not_empty(configs.as_slice(), input)
}
None => 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,
/// 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 +109,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 +145,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 { compilers: vec![] };
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 {
compilers: vec![Compiler {
path: std::path::PathBuf::from("cc"),
ignore: IgnoreOrConsider::Always,
arguments: Arguments::default(),
}],
};
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 {
compilers: vec![Compiler {
path: std::path::PathBuf::from("gcc"),
ignore: IgnoreOrConsider::Conditional,
arguments: Arguments {
match_: vec!["-O2".into()],
..Arguments::default()
},
}],
};
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 {
compilers: vec![Compiler {
path: std::path::PathBuf::from("gcc"),
ignore: IgnoreOrConsider::Never,
arguments: Arguments {
add: vec!["-Wall".into()],
remove: vec!["-O2".into()],
..Arguments::default()
},
}],
};
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 4624020

Please sign in to comment.