use std::{collections::HashMap, path::PathBuf, process::Command}; use yapall::{ analysis::{callgraph, int}, int::IntRelations, llvm::constant::Constant, llvm::instruction::{Call, Opcode}, Arc, Callee, FunctionName, Invoke, Module, Operand, TerminatorOpcode, UArc, }; // ------------------------------------------------------------------ // Helpers fn compile(prog: &str, dir: &str, opt: u8) -> llvm_ir::Module { let out = format!("tests/{}/{}-{}.bc", dir, prog, opt); let compiler = if prog.ends_with(".c") { "clang" } else { "clang++" }; let mut cmd = Command::new(compiler); cmd.arg("-emit-llvm") .arg("-c") .arg(format!("-O{}", opt)) .arg("-Werror") .arg("-o") .arg(&out) .arg(format!("tests/{}/{}", dir, prog)); let _status = cmd.output().unwrap(); // assert!(status.success()); // if prog.ends_with(".cpp") { // let status = Command::new("llvm-link") // .arg("--only-needed") // .arg(&out) // .arg("libcxx-7.1.0.bc") // .arg("-o") // .arg(&out) // .status() // .unwrap(); // assert!(status.success()); // } let path = PathBuf::from(&out); llvm_ir::Module::from_bc_path(path).unwrap() } fn convert(program: &str, dir: &str, opt: u8) -> Module { let llvm_module = compile(program, dir, opt); let mut operands: HashMap, &llvm_ir::Operand> = HashMap::new(); match Module::new(&llvm_module, &mut operands) { Ok(m) => m, Err(e) => panic!("{}", e), } } fn check(module: &Module) -> IntRelations { let cg = callgraph::analysis(&module); let outs = int::analysis(&module, &cg, 2, false, false); check_module(&outs, module); outs } fn check_module(out: &IntRelations, module: &Module) { for (f_name, f) in &module.functions { for b in &f.blocks { if let TerminatorOpcode::Invoke(Invoke { callee, args, .. }) = &*b.terminator.opcode.as_ref() { check_call(&out, &f_name, &callee, args); } for i in &b.instrs { if let Opcode::Call(Call { callee, args, .. }) = &*i.opcode.as_ref() { check_call(&out, &f_name, &callee, args); } } } } } fn check_call( out: &IntRelations, _caller: &UArc, callee: &Callee, arguments: &Vec>, ) { if let Callee::Operand(op) = &callee { if let Operand::Constant(const_arc) = &**op { if let Constant::Function(name) = &**const_arc { if **name == FunctionName::from("assert_constant") { for arg in arguments { let mut found = false; for ((_, op), val) in &out.operand_val { if op == arg { assert!(*val != yapall::IntLattice::top()); found = true; break; } } assert!(found); } } else if (**name).to_string().starts_with("@assert_") { panic!("Unknown assertion: {name}") } } } } } // ------------------------------------------------------------------ #[test] #[ignore] fn int_properties() { for program in std::fs::read_dir("tests/property").unwrap() { let path = program.as_ref().unwrap().path(); println!("{}", path.to_string_lossy()); if path.to_string_lossy().ends_with(".c") { // || path.to_string_lossy().ends_with(".cpp") { let module = convert( path.strip_prefix("tests/property") .unwrap() .to_str() .unwrap(), "property", 1, ); let _out = check(&module); } } } #[test] fn int_arg() { let module = convert("arg.c", "int", 1); let _out = check(&module); } #[test] fn int_const() { let module = convert("const.c", "int", 1); let _out = check(&module); } #[test] fn int_ret() { let module = convert("ret.c", "int", 1); let _out = check(&module); }