use crate::analysis::DominatorTree;
use crate::opcode::Opcode;
use crate::types::{Type, TypeKind};
use crate::value::{SubclassKind, ValueRef};
use std::collections::{HashMap, HashSet};
use std::rc::Rc;
#[derive(Debug, Clone)]
pub struct VerifierResult {
pub is_valid: bool,
pub errors: Vec<String>,
pub warnings: Vec<String>,
pub functions_verified: usize,
pub instructions_verified: usize,
}
impl VerifierResult {
pub fn success() -> Self {
Self {
is_valid: true,
errors: Vec::new(),
warnings: Vec::new(),
functions_verified: 0,
instructions_verified: 0,
}
}
pub fn fail(msg: impl Into<String>) -> Self {
Self {
is_valid: false,
errors: vec![msg.into()],
warnings: Vec::new(),
functions_verified: 0,
instructions_verified: 0,
}
}
pub fn warn(&mut self, msg: impl Into<String>) {
self.warnings.push(msg.into());
}
pub fn error(&mut self, msg: impl Into<String>) {
self.is_valid = false;
self.errors.push(msg.into());
}
}
pub fn verify_module(module: &crate::module::Module) -> VerifierResult {
let mut result = VerifierResult::success();
if module.target_triple.is_none() {
result.warn("Module has no target triple");
}
if module.data_layout.is_none() {
result.warn("Module has no data layout");
}
let mut names: HashSet<String> = HashSet::new();
for func in &module.functions {
let f = func.borrow();
if f.is_function() && !f.name.is_empty() && !names.insert(f.name.clone()) {
result.error(format!("Duplicate function name: @{}", f.name));
}
}
for func in &module.functions {
result.functions_verified += 1;
let func_result = verify_function_full(func);
result.instructions_verified += func_result.instructions_verified;
if !func_result.is_valid {
result.is_valid = false;
result.errors.extend(func_result.errors);
}
result.warnings.extend(func_result.warnings);
}
result
}
pub fn verify_function_full(func: &ValueRef) -> VerifierResult {
let f = func.borrow();
let mut result = VerifierResult::success();
if !f.is_function() {
result.error(format!("Expected Function, got {:?}", f.subclass));
return result;
}
if f.name.is_empty() {
result.warn("Function has no name");
}
verify_function_return_type(&f.ty, &mut result);
let has_entry = f
.operands
.first()
.map(|op| op.borrow().is_basic_block())
.unwrap_or(false);
if !has_entry && !f.operands.is_empty() {
result.error("Function must have an entry basic block");
}
if f.operands.is_empty() {
return result;
}
let dt = DominatorTree::compute(func);
let mut block_map: HashMap<String, usize> = HashMap::new();
let mut block_names: Vec<String> = Vec::new();
let mut blocks: Vec<ValueRef> = Vec::new();
for (i, op) in f.operands.iter().enumerate() {
let bb = op.borrow();
if bb.is_basic_block() {
block_map.insert(bb.name.clone(), i);
block_names.push(bb.name.clone());
blocks.push(op.clone());
}
}
if blocks.is_empty() {
result.error("Function has no basic blocks");
return result;
}
for (i, block_val) in blocks.iter().enumerate() {
let bb = block_val.borrow();
result.instructions_verified += bb.operands.len();
if !bb.is_basic_block() {
result.error(format!("Operand {} is not a basic block", i));
continue;
}
if !bb.ty.is_label() {
result.error(format!("Block '{}' must have label type", bb.name));
}
verify_basic_block_structure(block_val, &mut result, &dt, &block_map, i);
}
verify_ssa_dominance(func, &mut result, &dt, &block_map, &blocks);
result
}
fn verify_function_return_type(ty: &Type, result: &mut VerifierResult) {
match &ty.kind {
TypeKind::Void => {} TypeKind::Integer { .. }
| TypeKind::Float
| TypeKind::Double
| TypeKind::Pointer { .. }
| TypeKind::Struct { .. }
| TypeKind::Array { .. }
| TypeKind::FixedVector { .. }
| TypeKind::ScalableVector { .. } => {} TypeKind::Label => result.error("Function return type cannot be label"),
TypeKind::Metadata => result.error("Function return type cannot be metadata"),
TypeKind::Token => result.error("Function return type cannot be token"),
TypeKind::Function { .. } => {} _ => {}
}
}
fn verify_basic_block_structure(
block_val: &ValueRef,
result: &mut VerifierResult,
_dt: &DominatorTree,
block_map: &HashMap<String, usize>,
_block_index: usize,
) {
let bb = block_val.borrow();
if bb.operands.is_empty() {
result.error(format!(
"Block '{}' has no instructions (no terminator)",
bb.name
));
return;
}
let mut seen_non_phi = false;
for (i, inst_val) in bb.operands.iter().enumerate() {
let inst = inst_val.borrow();
if !inst.is_instruction() {
continue;
}
let is_phi = inst.get_opcode() == Some(Opcode::Phi);
if is_phi && seen_non_phi {
result.error(format!(
"Block '{}': phi node after non-phi instruction at position {}",
bb.name, i
));
}
if !is_phi {
seen_non_phi = true;
}
}
let last_inst = bb.operands.last().unwrap().borrow();
if last_inst.is_instruction() {
let is_terminator = last_inst
.get_opcode()
.map(|o| o.is_terminator())
.unwrap_or(false);
if !is_terminator {
result.error(format!(
"Block '{}': last instruction must be a terminator, got {:?}",
bb.name,
last_inst.get_opcode()
));
}
}
if let Some(last_val) = bb.operands.last() {
let last = last_val.borrow();
if last.is_instruction() {
match last.get_opcode() {
Some(Opcode::Br) if last.operands.len() == 1 => {
let dest_name = last.operands[0].borrow().name.clone();
if !block_map.contains_key(&dest_name) {
result.error(format!(
"Block '{}': unconditional branch to unknown block '{}'",
bb.name, dest_name
));
}
}
Some(Opcode::Br) if last.operands.len() == 3 => {
for &op_idx in &[1usize, 2] {
let dest_name = last.operands[op_idx].borrow().name.clone();
if !block_map.contains_key(&dest_name) {
result.error(format!(
"Block '{}': conditional branch to unknown block '{}'",
bb.name, dest_name
));
}
}
}
Some(Opcode::Ret) => {} Some(Opcode::Unreachable) => {} _ => {}
}
}
}
}
fn verify_ssa_dominance(
_func: &ValueRef,
result: &mut VerifierResult,
dt: &DominatorTree,
_block_map: &HashMap<String, usize>,
blocks: &[ValueRef],
) {
let mut def_map: HashMap<u64, (usize, usize)> = HashMap::new();
for (block_idx, block_val) in blocks.iter().enumerate() {
let bb = block_val.borrow();
for (pos, inst_val) in bb.operands.iter().enumerate() {
let inst = inst_val.borrow();
if inst.is_instruction() {
def_map.insert(inst.vid, (block_idx, pos));
}
}
}
for (block_idx, block_val) in blocks.iter().enumerate() {
let bb = block_val.borrow();
for inst_val in &bb.operands {
let inst = inst_val.borrow();
if !inst.is_instruction() {
continue;
}
if inst.get_opcode() == Some(Opcode::Phi) {
continue;
}
for operand_val in &inst.operands {
let op = operand_val.borrow();
if op.is_constant() || op.is_function() || op.is_basic_block() {
continue;
}
if let Some(&(def_block, _def_pos)) = def_map.get(&op.vid) {
if !dt.dominates(def_block, block_idx) {
result.error(format!(
"Instruction '%{}' in block '{}' uses value '%{}' defined in block '{}' which does not dominate it",
inst.name, bb.name, op.name, blocks.get(def_block)
.map(|b| b.borrow().name.clone())
.unwrap_or_else(|| "?".into())
));
}
}
}
}
}
}
pub fn verify_instruction(inst: &ValueRef) -> VerifierResult {
let i = inst.borrow();
let mut result = VerifierResult::success();
if !i.is_instruction() {
result.error(format!("Expected Instruction, got {:?}", i.subclass));
return result;
}
let opcode = i.get_opcode();
let num_ops = i.operands.len();
match opcode {
Some(Opcode::Add) | Some(Opcode::Sub) | Some(Opcode::Mul) | Some(Opcode::UDiv)
| Some(Opcode::SDiv) | Some(Opcode::URem) | Some(Opcode::SRem) | Some(Opcode::Shl)
| Some(Opcode::LShr) | Some(Opcode::AShr) | Some(Opcode::And) | Some(Opcode::Or)
| Some(Opcode::Xor) => {
if num_ops != 2 {
result.error(format!(
"{} requires 2 operands, got {}",
opcode.unwrap(),
num_ops
));
} else {
let op0_ty = i.operands[0].borrow().ty.clone();
let op1_ty = i.operands[1].borrow().ty.clone();
if op0_ty.kind != op1_ty.kind {
result.warn(format!(
"{} operand types differ: {} vs {}",
opcode.unwrap(),
op0_ty,
op1_ty
));
}
if i.ty.kind != op0_ty.kind {
result.warn(format!(
"{} result type {} doesn't match operand type {}",
opcode.unwrap(),
i.ty,
op0_ty
));
}
}
}
Some(Opcode::FAdd) | Some(Opcode::FSub) | Some(Opcode::FMul) | Some(Opcode::FDiv)
| Some(Opcode::FRem) => {
if num_ops != 2 {
result.error(format!("{} requires 2 operands", opcode.unwrap()));
}
if !i.ty.is_floating_point() {
result.error(format!("{} result must be floating point", opcode.unwrap()));
}
}
Some(Opcode::ICmp) => {
if num_ops != 2 {
result.error("icmp requires 2 operands");
}
if !i.ty.is_integer() || i.ty.integer_bit_width() != 1 {
result.error("icmp result must be i1");
}
}
Some(Opcode::Alloca) => {
if !i.ty.is_pointer() {
result.error("alloca result must be a pointer type");
}
}
Some(Opcode::Load) => {
if num_ops != 1 {
result.error("load requires 1 operand (pointer)");
}
}
Some(Opcode::Store) => {
if num_ops != 2 {
result.error("store requires 2 operands (value, pointer)");
}
if !i.ty.is_void() {
result.warn("store result should be void");
}
}
Some(Opcode::Br) => {
if num_ops != 1 && num_ops != 3 {
result.error(format!("br requires 1 or 3 operands, got {}", num_ops));
}
if num_ops == 3 {
if !i.operands[0].borrow().ty.is_integer()
|| i.operands[0].borrow().ty.integer_bit_width() != 1
{
result.warn("br condition should be i1");
}
if !i.operands[1].borrow().is_basic_block()
|| !i.operands[2].borrow().is_basic_block()
{
result.error("br targets must be basic blocks");
}
} else if num_ops == 1 && !i.operands[0].borrow().is_basic_block() {
result.error("br target must be a basic block");
}
}
Some(Opcode::Ret) => {
if num_ops > 1 {
result.error(format!("ret requires 0 or 1 operands, got {}", num_ops));
}
}
Some(Opcode::Call) => {
if num_ops < 1 {
result.error("call requires at least 1 operand (function)");
}
}
Some(Opcode::Phi) => {
if num_ops == 0 || !num_ops.is_multiple_of(2) {
result.error(format!(
"phi requires even number of operands, got {}",
num_ops
));
}
}
Some(Opcode::Unreachable) if num_ops != 0 => {
result.warn("unreachable should have no operands");
}
None => {
result.warn("Instruction has no opcode set");
}
_ => {} }
result
}
pub fn verify_function(func: &ValueRef) -> VerifierResult {
verify_function_full(func)
}
pub fn verify_basic_block(block: &ValueRef) -> VerifierResult {
let b = block.borrow();
let mut result = VerifierResult::success();
if !b.is_basic_block() {
result.error(format!("Expected BasicBlock, got {:?}", b.subclass));
return result;
}
if !b.ty.is_label() {
result.error("BasicBlock must have label type");
}
if b.operands.is_empty() {
result.error(format!("Block '{}' has no terminator", b.name));
} else {
let last = b.operands.last().unwrap().borrow();
if last.is_instruction() {
let is_terminator = last
.get_opcode()
.map(|o| o.is_terminator())
.unwrap_or(false);
if !is_terminator {
result.error(format!("Block '{}' does not end with a terminator", b.name));
}
}
}
result
}
pub fn verify_dominance(func: &ValueRef) -> VerifierResult {
let mut result = VerifierResult::success();
let dt = DominatorTree::compute(func);
let mut block_map: HashMap<String, usize> = HashMap::new();
let mut blocks: Vec<ValueRef> = Vec::new();
{
let f = func.borrow();
for (i, op) in f.operands.iter().enumerate() {
let bb = op.borrow();
if bb.is_basic_block() {
block_map.insert(bb.name.clone(), i);
blocks.push(op.clone());
}
}
}
verify_ssa_dominance(func, &mut result, &dt, &block_map, &blocks);
result
}
use crate::pass_manager::{AnalysisManager, FunctionPass, ModulePass, Pass, PassResult};
pub struct VerifierPass;
impl Pass for VerifierPass {
fn name(&self) -> &'static str {
"verify"
}
}
impl FunctionPass for VerifierPass {
fn run_on_function(&mut self, func: &ValueRef, _am: &AnalysisManager) -> PassResult {
let result = verify_function_full(func);
if !result.is_valid {
eprintln!("Verifier errors in function:");
for e in &result.errors {
eprintln!(" {}", e);
}
PassResult::Error
} else {
PassResult::Unchanged
}
}
}
pub struct ModuleVerifierPass;
impl Pass for ModuleVerifierPass {
fn name(&self) -> &'static str {
"module-verify"
}
}
impl ModulePass for ModuleVerifierPass {
fn run_on_module(
&mut self,
module: &mut crate::module::Module,
_am: &mut AnalysisManager,
) -> PassResult {
let result = verify_module(module);
if !result.is_valid {
eprintln!("Module verifier errors:");
for e in &result.errors {
eprintln!(" {}", e);
}
PassResult::Error
} else {
PassResult::Unchanged
}
}
}
#[derive(Debug, Clone)]
pub struct VerifierError {
pub message: String,
pub context: Option<String>,
}
#[derive(Debug, Clone)]
pub struct VerifierWarning {
pub message: String,
}
pub struct ModuleVerifier {
pub errors: Vec<VerifierError>,
pub warnings: Vec<VerifierWarning>,
}
impl ModuleVerifier {
pub fn new() -> Self {
Self {
errors: Vec::new(),
warnings: Vec::new(),
}
}
pub fn verify_module(module: &crate::module::Module) -> Result<(), Vec<VerifierError>> {
let mut v = ModuleVerifier::new();
v.verify_module_structure(module);
v.verify_target_triple(module);
v.verify_data_layout(module);
for func in &module.functions {
v.verify_function(func);
}
for gv in &module.globals {
v.verify_global_variable(gv);
}
if v.errors.is_empty() {
Ok(())
} else {
Err(v.errors)
}
}
fn verify_module_structure(&mut self, module: &crate::module::Module) {
let mut func_names: HashSet<String> = HashSet::new();
for func in &module.functions {
let name = func.borrow().name.clone();
if name.is_empty() {
self.error("Module contains function with empty name".to_string(), None);
} else if !func_names.insert(name.clone()) {
self.error(
format!("Duplicate function name: @{}", name),
Some(name.clone()),
);
}
}
let mut gv_names: HashSet<String> = HashSet::new();
for gv in &module.globals {
let name = gv.borrow().name.clone();
if name.is_empty() {
self.error("Module contains global with empty name".to_string(), None);
} else if !gv_names.insert(name.clone()) {
self.error(
format!("Duplicate global name: @{}", name),
Some(name.clone()),
);
}
}
}
fn verify_target_triple(&mut self, module: &crate::module::Module) {
if module.target_triple.is_none() {
self.warn("Module has no target triple".to_string());
} else if let Some(ref t) = module.target_triple {
if t.is_empty() {
self.warn("Module target triple is empty".to_string());
}
}
}
fn verify_data_layout(&mut self, module: &crate::module::Module) {
if module.data_layout.is_none() {
self.warn("Module has no data layout".to_string());
}
}
fn verify_function(&mut self, func: &ValueRef) {
self.verify_function_signature(func);
self.verify_function_body(func);
self.verify_attributes(func);
self.verify_param_attrs_compatible(func);
self.verify_cfg(func);
self.verify_dominance(func);
self.verify_reachability(func);
}
fn verify_function_signature(&mut self, func: &ValueRef) {
let f = func.borrow();
if !f.is_function() {
self.error(
format!("Expected Function, got {:?}", f.subclass),
Some(f.name.clone()),
);
return;
}
match &f.ty.kind {
TypeKind::Void
| TypeKind::Integer { .. }
| TypeKind::Float
| TypeKind::Double
| TypeKind::Pointer { .. }
| TypeKind::Struct { .. }
| TypeKind::Array { .. }
| TypeKind::FixedVector { .. }
| TypeKind::ScalableVector { .. }
| TypeKind::Function { .. } => {}
TypeKind::Label => self.error(
format!("Function '{}' return type cannot be label", f.name),
Some(f.name.clone()),
),
TypeKind::Metadata => self.error(
format!("Function '{}' return type cannot be metadata", f.name),
Some(f.name.clone()),
),
TypeKind::Token => self.error(
format!("Function '{}' return type cannot be token", f.name),
Some(f.name.clone()),
),
_ => {}
}
}
fn verify_function_body(&mut self, func: &ValueRef) {
let f = func.borrow();
let func_name = f.name.clone();
if f.operands.is_empty() {
return;
}
let has_entry = f
.operands
.first()
.map(|op| op.borrow().is_basic_block())
.unwrap_or(false);
if !has_entry {
self.error(
format!("Function '{}' must have an entry basic block", func_name),
Some(func_name.clone()),
);
return;
}
let mut block_map: HashMap<String, usize> = HashMap::new();
let mut blocks: Vec<ValueRef> = Vec::new();
for (i, op) in f.operands.iter().enumerate() {
let bb = op.borrow();
if bb.is_basic_block() {
block_map.insert(bb.name.clone(), i);
blocks.push(op.clone());
}
}
if blocks.is_empty() {
self.error(
format!("Function '{}' has no basic blocks", func_name),
Some(func_name),
);
return;
}
for (i, block_val) in blocks.iter().enumerate() {
self.verify_basic_block(block_val, func);
}
}
fn verify_basic_block(&mut self, bb: &ValueRef, func: &ValueRef) {
let b = bb.borrow();
let bb_name = b.name.clone();
if !b.is_basic_block() {
self.error(
format!("Expected BasicBlock, got non-block value '{}'", bb_name),
Some(bb_name.clone()),
);
return;
}
if !b.ty.is_label() {
self.error(
format!("Block '{}' must have label type", bb_name),
Some(bb_name.clone()),
);
}
if b.operands.is_empty() {
self.error(
format!("Block '{}' has no instructions (no terminator)", bb_name),
Some(bb_name.clone()),
);
return;
}
self.verify_phi_nodes_at_start(bb);
self.verify_single_terminator(bb);
self.verify_terminator(bb);
for inst_val in &b.operands {
self.verify_instruction(inst_val, bb);
}
}
fn verify_single_terminator(&mut self, bb: &ValueRef) {
let b = bb.borrow();
let mut terminator_count = 0usize;
for inst_val in &b.operands {
let inst = inst_val.borrow();
if inst.is_instruction() {
if let Some(op) = inst.get_opcode() {
if op.is_terminator() {
terminator_count += 1;
}
}
}
}
if terminator_count == 0 {
self.error(
format!("Block '{}' has no terminator instruction", b.name),
Some(b.name.clone()),
);
} else if terminator_count > 1 {
self.error(
format!(
"Block '{}' has {} terminators; only one allowed",
b.name, terminator_count
),
Some(b.name.clone()),
);
}
}
fn verify_phi_nodes_at_start(&mut self, bb: &ValueRef) {
let b = bb.borrow();
let mut seen_non_phi = false;
for (i, inst_val) in b.operands.iter().enumerate() {
let inst = inst_val.borrow();
if !inst.is_instruction() {
continue;
}
let is_phi = inst.get_opcode() == Some(Opcode::Phi);
if is_phi && seen_non_phi {
self.error(
format!(
"Block '{}': phi node at position {} appears after non-phi instruction",
b.name, i
),
Some(b.name.clone()),
);
}
if !is_phi {
seen_non_phi = true;
}
}
}
fn verify_terminator(&mut self, bb: &ValueRef) {
let b = bb.borrow();
let last = match b.operands.last() {
Some(val) => val.borrow(),
None => return,
};
if !last.is_instruction() {
return;
}
match last.get_opcode() {
Some(Opcode::Ret) => {
}
Some(Opcode::Br) => {
if last.operands.len() == 1 {
let target = last.operands[0].borrow();
if !target.is_basic_block() {
self.error(
format!(
"Block '{}': unconditional br target must be a basic block",
b.name
),
Some(b.name.clone()),
);
}
} else if last.operands.len() == 3 {
for idx in &[1usize, 2] {
let target = last.operands[*idx].borrow();
if !target.is_basic_block() {
self.error(
format!(
"Block '{}': conditional br target #{} must be a basic block",
b.name,
idx - 1
),
Some(b.name.clone()),
);
}
}
}
}
Some(Opcode::Switch) => {
if !last.operands.is_empty() {
let default_dest = last.operands.last().unwrap().borrow();
if !default_dest.is_basic_block() {
self.error(
format!("Block '{}': switch default must be a basic block", b.name),
Some(b.name.clone()),
);
}
}
}
Some(Opcode::Invoke) => {
}
Some(Opcode::Unreachable) => {} _ => {
self.error(
format!(
"Block '{}': last instruction must be a terminator, got {:?}",
b.name,
last.get_opcode()
),
Some(b.name.clone()),
);
}
}
}
fn verify_instruction(&mut self, inst: &ValueRef, bb: &ValueRef) {
let i = inst.borrow();
if !i.is_instruction() {
return;
}
self.verify_instruction_operands(inst);
self.verify_type_consistency(inst);
self.verify_use_before_def(inst);
match i.get_opcode() {
Some(Opcode::Ret) => self.verify_ret(inst, &i),
Some(Opcode::Br) => self.verify_br(inst, &i),
Some(Opcode::Switch) => self.verify_switch(inst),
Some(Opcode::Phi) => self.verify_phi(inst, bb, &i),
Some(Opcode::Call) => self.verify_call(inst, &i),
Some(Opcode::Load) => self.verify_load(inst),
Some(Opcode::Store) => self.verify_store(inst),
Some(Opcode::Alloca) => self.verify_alloca(inst),
Some(Opcode::GetElementPtr) => self.verify_gep(inst),
Some(Opcode::BitCast)
| Some(Opcode::IntToPtr)
| Some(Opcode::PtrToInt)
| Some(Opcode::Trunc)
| Some(Opcode::ZExt)
| Some(Opcode::SExt)
| Some(Opcode::FPTrunc)
| Some(Opcode::FPExt)
| Some(Opcode::FPToUI)
| Some(Opcode::FPToSI)
| Some(Opcode::UIToFP)
| Some(Opcode::SIToFP) => self.verify_cast(inst),
Some(Opcode::ICmp) => self.verify_icmp(inst),
Some(Opcode::FCmp) => self.verify_fcmp(inst),
Some(Opcode::Select) => self.verify_select(inst),
Some(Opcode::ExtractValue) => self.verify_extract_value(inst),
Some(Opcode::InsertValue) => self.verify_insert_value(inst),
Some(Opcode::AtomicRMW) | Some(Opcode::CmpXchg) | Some(Opcode::Fence) => {
self.verify_atomic_ops(inst)
}
Some(Opcode::LandingPad) => self.verify_landingpad(inst),
Some(Opcode::Invoke) => self.verify_invoke(inst, &i),
Some(Opcode::Unreachable) => {} _ => {}
}
}
fn verify_instruction_operands(&mut self, inst: &ValueRef) {
let i = inst.borrow();
let num_ops = i.operands.len();
match i.get_opcode() {
Some(Opcode::Add) | Some(Opcode::Sub) | Some(Opcode::Mul) | Some(Opcode::UDiv)
| Some(Opcode::SDiv) | Some(Opcode::URem) | Some(Opcode::SRem) | Some(Opcode::Shl)
| Some(Opcode::LShr) | Some(Opcode::AShr) | Some(Opcode::And) | Some(Opcode::Or)
| Some(Opcode::Xor) | Some(Opcode::FAdd) | Some(Opcode::FSub) | Some(Opcode::FMul)
| Some(Opcode::FDiv) | Some(Opcode::FRem) => {
if num_ops != 2 {
self.error(
format!(
"{} requires 2 operands, got {}",
i.get_opcode().unwrap(),
num_ops
),
Some(i.name.clone()),
);
}
}
Some(Opcode::ICmp) | Some(Opcode::FCmp) => {
if num_ops != 2 {
self.error(
format!(
"{} requires 2 operands, got {}",
i.get_opcode().unwrap(),
num_ops
),
Some(i.name.clone()),
);
}
}
Some(Opcode::Select) => {
if num_ops != 3 {
self.error(
format!("select requires 3 operands, got {}", num_ops),
Some(i.name.clone()),
);
}
}
Some(Opcode::ExtractValue) => {
if num_ops < 2 {
self.error(
format!(
"extractvalue requires at least 2 operands (aggregate + indices), got {}",
num_ops
),
Some(i.name.clone()),
);
}
}
Some(Opcode::InsertValue) => {
if num_ops < 3 {
self.error(
format!("insertvalue requires at least 3 operands, got {}", num_ops),
Some(i.name.clone()),
);
}
}
_ => {}
}
}
fn verify_type_consistency(&mut self, inst: &ValueRef) {
let i = inst.borrow();
match i.get_opcode() {
Some(Opcode::Add) | Some(Opcode::Sub) | Some(Opcode::Mul) | Some(Opcode::UDiv)
| Some(Opcode::SDiv) | Some(Opcode::URem) | Some(Opcode::SRem) | Some(Opcode::Shl)
| Some(Opcode::LShr) | Some(Opcode::AShr) | Some(Opcode::And) | Some(Opcode::Or)
| Some(Opcode::Xor) => {
if i.operands.len() == 2 {
let t0 = i.operands[0].borrow().ty.clone();
let t1 = i.operands[1].borrow().ty.clone();
if t0.kind != t1.kind {
self.warn(format!(
"{}: operand types differ ({} vs {})",
i.get_opcode().unwrap(),
t0,
t1
));
}
}
}
Some(Opcode::ICmp) => {
if !i.ty.is_integer() || i.ty.integer_bit_width() != 1 {
self.error(
"icmp result type must be i1".to_string(),
Some(i.name.clone()),
);
}
}
Some(Opcode::FCmp) => {
if !i.ty.is_integer() || i.ty.integer_bit_width() != 1 {
self.error(
"fcmp result type must be i1".to_string(),
Some(i.name.clone()),
);
}
}
Some(Opcode::Select) => {
if i.operands.len() == 3 {
let cond_ty = &i.operands[0].borrow().ty;
if !cond_ty.is_integer() || cond_ty.integer_bit_width() != 1 {
self.warn(format!("select condition should be i1, got {}", cond_ty));
}
}
}
_ => {}
}
}
fn verify_use_before_def(&mut self, inst: &ValueRef) {
let _i = inst.borrow();
}
fn verify_ret(&mut self, _inst: &ValueRef, _i: &crate::value::Value) {
}
fn verify_br(&mut self, _inst: &ValueRef, i: &crate::value::Value) {
let num_ops = i.operands.len();
if num_ops == 1 {
let target = i.operands[0].borrow();
if !target.is_basic_block() {
self.error(
"br: target must be a basic block".to_string(),
Some(i.name.clone()),
);
}
} else if num_ops == 3 {
let cond = i.operands[0].borrow();
if !cond.ty.is_integer() || cond.ty.integer_bit_width() != 1 {
self.warn(format!("br: condition should be i1, got {}", cond.ty));
}
for op_idx in &[1usize, 2] {
if !i.operands[*op_idx].borrow().is_basic_block() {
self.error(
format!("br: operand {} must be a basic block", op_idx),
Some(i.name.clone()),
);
}
}
} else {
self.error(
format!("br: requires 1 or 3 operands, got {}", num_ops),
Some(i.name.clone()),
);
}
}
fn verify_switch(&mut self, inst: &ValueRef) {
let i = inst.borrow();
let num_ops = i.operands.len();
if num_ops < 2 {
self.error(
format!("switch requires at least 2 operands, got {}", num_ops),
Some(i.name.clone()),
);
return;
}
let val_ty = &i.operands[0].borrow().ty;
if !val_ty.is_integer() {
self.warn(format!("switch value should be integer, got {}", val_ty));
}
let default_dest = i.operands[num_ops - 1].borrow();
if !default_dest.is_basic_block() {
self.error(
"switch: default destination must be a basic block".to_string(),
Some(i.name.clone()),
);
}
let pair_count = (num_ops - 2) / 2;
let mut case_values: HashSet<String> = HashSet::new();
for p in 0..pair_count {
let case_idx = 1 + p * 2;
let dest_idx = case_idx + 1;
let case_val_ref = &i.operands[case_idx];
let case_dest_ref = &i.operands[dest_idx];
let cv = case_val_ref.borrow();
let cd = case_dest_ref.borrow();
let case_key = cv.name.clone();
if !case_key.is_empty() && !case_values.insert(case_key.clone()) {
self.warn(format!("switch: duplicate case value '{}'", case_key));
}
if !cd.is_basic_block() {
self.error(
format!(
"switch: case destination at index {} must be a basic block",
p
),
Some(i.name.clone()),
);
}
}
}
fn verify_phi(&mut self, inst: &ValueRef, bb: &ValueRef, i: &crate::value::Value) {
let num_ops = i.operands.len();
if num_ops == 0 || num_ops % 2 != 0 {
self.error(
format!(
"phi: requires even number of operands (value, label pairs), got {}",
num_ops
),
Some(i.name.clone()),
);
return;
}
let pair_count = num_ops / 2;
if pair_count > 1 {
let first_val_ty = &i.operands[0].borrow().ty;
for p in 1..pair_count {
let val_ty = &i.operands[p * 2].borrow().ty;
if val_ty.kind != first_val_ty.kind {
self.error(
format!(
"phi: incoming value types differ: {} vs {}",
first_val_ty, val_ty
),
Some(i.name.clone()),
);
}
}
}
for p in 0..pair_count {
let label = i.operands[p * 2 + 1].borrow();
if !label.is_basic_block() {
self.error(
format!(
"phi: incoming block at position {} must be a basic block",
p
),
Some(i.name.clone()),
);
}
}
}
fn verify_call(&mut self, inst: &ValueRef, i: &crate::value::Value) {
let num_ops = i.operands.len();
if num_ops < 1 {
self.error(
"call: requires at least 1 operand (callee)".to_string(),
Some(i.name.clone()),
);
return;
}
let callee = i.operands[0].borrow();
if !callee.is_function() {
if !callee.ty.is_pointer() {
self.warn(format!(
"call: callee '{}' may not be a callable function",
callee.name
));
}
}
if callee.is_function() && callee.operands.len() > 0 {
}
}
fn verify_load(&mut self, inst: &ValueRef) {
let i = inst.borrow();
if i.operands.len() != 1 {
self.error(
format!(
"load: requires 1 operand (pointer), got {}",
i.operands.len()
),
Some(i.name.clone()),
);
return;
}
let ptr_ty = &i.operands[0].borrow().ty;
if !ptr_ty.is_pointer() {
self.error(
format!("load: operand must be a pointer type, got {}", ptr_ty),
Some(i.name.clone()),
);
}
if i.ty.is_void() {
self.error(
"load: result type cannot be void".to_string(),
Some(i.name.clone()),
);
}
}
fn verify_store(&mut self, inst: &ValueRef) {
let i = inst.borrow();
if i.operands.len() != 2 {
self.error(
format!(
"store: requires 2 operands (value, pointer), got {}",
i.operands.len()
),
Some(i.name.clone()),
);
return;
}
let ptr_ty = &i.operands[1].borrow().ty;
if !ptr_ty.is_pointer() {
self.error(
format!(
"store: second operand must be a pointer type, got {}",
ptr_ty
),
Some(i.name.clone()),
);
}
}
fn verify_alloca(&mut self, inst: &ValueRef) {
let i = inst.borrow();
if !i.ty.is_pointer() {
self.error(
"alloca: result type must be a pointer".to_string(),
Some(i.name.clone()),
);
}
if i.operands.len() >= 1 {
let size_op = i.operands[0].borrow();
if !size_op.ty.is_integer() {
self.warn("alloca: size operand should be integer type".to_string());
}
}
}
fn verify_gep(&mut self, inst: &ValueRef) {
let i = inst.borrow();
if i.operands.is_empty() {
self.error(
"getelementptr: requires at least 1 operand (base pointer)".to_string(),
Some(i.name.clone()),
);
return;
}
let base_ty = &i.operands[0].borrow().ty;
if !base_ty.is_pointer() {
self.error(
format!(
"getelementptr: base operand must be a pointer type, got {}",
base_ty
),
Some(i.name.clone()),
);
}
for (idx, op) in i.operands.iter().skip(1).enumerate() {
let op_ty = &op.borrow().ty;
if !op_ty.is_integer() {
self.error(
format!(
"getelementptr: index {} must be integer type, got {}",
idx + 1,
op_ty
),
Some(i.name.clone()),
);
}
}
}
fn verify_cast(&mut self, inst: &ValueRef) {
let i = inst.borrow();
if i.operands.is_empty() {
self.error("cast: requires 1 operand".to_string(), Some(i.name.clone()));
return;
}
let src_ty = &i.operands[0].borrow().ty;
let dst_ty = &i.ty;
match i.get_opcode() {
Some(Opcode::BitCast) => {
if matches!(
src_ty.kind,
TypeKind::Void | TypeKind::Label | TypeKind::Metadata | TypeKind::Token
) {
self.error(
"bitcast: source type cannot be void/label/metadata/token".to_string(),
Some(i.name.clone()),
);
}
}
Some(Opcode::Trunc) => {
if !src_ty.is_integer() || !dst_ty.is_integer() {
self.error(
"trunc: both types must be integer".to_string(),
Some(i.name.clone()),
);
}
}
Some(Opcode::ZExt) | Some(Opcode::SExt) => {
if !src_ty.is_integer() || !dst_ty.is_integer() {
self.error(
"ext: both types must be integer".to_string(),
Some(i.name.clone()),
);
}
}
Some(Opcode::FPTrunc) | Some(Opcode::FPExt) => {
if !src_ty.is_floating_point() || !dst_ty.is_floating_point() {
self.error(
"fp cast: both types must be floating point".to_string(),
Some(i.name.clone()),
);
}
}
Some(Opcode::IntToPtr) => {
if !src_ty.is_integer() {
self.error(
"inttoptr: source must be integer".to_string(),
Some(i.name.clone()),
);
}
if !dst_ty.is_pointer() {
self.error(
"inttoptr: destination must be pointer".to_string(),
Some(i.name.clone()),
);
}
}
Some(Opcode::PtrToInt) => {
if !src_ty.is_pointer() {
self.error(
"ptrtoint: source must be pointer".to_string(),
Some(i.name.clone()),
);
}
if !dst_ty.is_integer() {
self.error(
"ptrtoint: destination must be integer".to_string(),
Some(i.name.clone()),
);
}
}
_ => {}
}
}
fn verify_icmp(&mut self, inst: &ValueRef) {
let i = inst.borrow();
if i.operands.len() != 2 {
self.error(
format!("icmp: requires 2 operands, got {}", i.operands.len()),
Some(i.name.clone()),
);
return;
}
if !i.ty.is_integer() || i.ty.integer_bit_width() != 1 {
self.error(
"icmp: result type must be i1".to_string(),
Some(i.name.clone()),
);
}
let t0 = &i.operands[0].borrow().ty;
let t1 = &i.operands[1].borrow().ty;
if !t0.is_integer() || !t1.is_integer() {
self.warn("icmp: operand types should be integer".to_string());
}
if t0.kind != t1.kind {
self.warn(format!("icmp: operand types differ ({} vs {})", t0, t1));
}
}
fn verify_fcmp(&mut self, inst: &ValueRef) {
let i = inst.borrow();
if i.operands.len() != 2 {
self.error(
format!("fcmp: requires 2 operands, got {}", i.operands.len()),
Some(i.name.clone()),
);
return;
}
if !i.ty.is_integer() || i.ty.integer_bit_width() != 1 {
self.error(
"fcmp: result type must be i1".to_string(),
Some(i.name.clone()),
);
}
let t0 = &i.operands[0].borrow().ty;
let t1 = &i.operands[1].borrow().ty;
if !t0.is_floating_point() || !t1.is_floating_point() {
self.error(
"fcmp: both operands must be floating point types".to_string(),
Some(i.name.clone()),
);
}
}
fn verify_select(&mut self, inst: &ValueRef) {
let i = inst.borrow();
if i.operands.len() != 3 {
self.error(
format!("select: requires 3 operands, got {}", i.operands.len()),
Some(i.name.clone()),
);
return;
}
let cond_ty = &i.operands[0].borrow().ty;
if !cond_ty.is_integer() || cond_ty.integer_bit_width() != 1 {
self.warn(format!("select: condition should be i1, got {}", cond_ty));
}
let true_ty = &i.operands[1].borrow().ty;
let false_ty = &i.operands[2].borrow().ty;
if true_ty.kind != false_ty.kind {
self.warn(format!(
"select: true/false value types differ ({} vs {})",
true_ty, false_ty
));
}
}
fn verify_extract_value(&mut self, inst: &ValueRef) {
let i = inst.borrow();
if i.operands.is_empty() {
self.error(
"extractvalue: requires at least 1 operand (aggregate)".to_string(),
Some(i.name.clone()),
);
return;
}
let agg_ty = &i.operands[0].borrow().ty;
if !matches!(
agg_ty.kind,
TypeKind::Struct { .. } | TypeKind::Array { .. }
) {
self.warn(format!(
"extractvalue: base operand should be aggregate type, got {}",
agg_ty
));
}
}
fn verify_insert_value(&mut self, inst: &ValueRef) {
let i = inst.borrow();
if i.operands.len() < 2 {
self.error(
format!(
"insertvalue: requires at least 2 operands, got {}",
i.operands.len()
),
Some(i.name.clone()),
);
}
}
fn verify_atomic_ops(&mut self, inst: &ValueRef) {
let i = inst.borrow();
let opcode = i.get_opcode();
match opcode {
Some(Opcode::AtomicRMW) => {
if i.operands.len() < 2 {
self.error(
"atomicrmw: requires at least 2 operands (ptr, value)".to_string(),
Some(i.name.clone()),
);
}
}
Some(Opcode::CmpXchg) => {
if i.operands.len() < 3 {
self.error(
"cmpxchg: requires at least 3 operands (ptr, cmp, new)".to_string(),
Some(i.name.clone()),
);
}
}
Some(Opcode::Fence) => {
}
_ => {}
}
}
fn verify_landingpad(&mut self, _inst: &ValueRef) {
let i = _inst.borrow();
if i.ty.is_void() {
self.error(
"landingpad: result type cannot be void".to_string(),
Some(i.name.clone()),
);
}
}
fn verify_invoke(&mut self, _inst: &ValueRef, i: &crate::value::Value) {
let num_ops = i.operands.len();
if num_ops < 1 {
self.error(
"invoke: requires at least 1 operand (callee)".to_string(),
Some(i.name.clone()),
);
return;
}
let callee = i.operands[0].borrow();
if !callee.is_function() && !callee.ty.is_pointer() {
self.warn(format!(
"invoke: callee '{}' may not be callable",
callee.name
));
}
}
fn verify_cfg(&mut self, func: &ValueRef) {
let f = func.borrow();
let mut block_names: HashSet<String> = HashSet::new();
let mut blocks: Vec<(String, ValueRef)> = Vec::new();
for op in &f.operands {
let bb = op.borrow();
if bb.is_basic_block() {
block_names.insert(bb.name.clone());
blocks.push((bb.name.clone(), op.clone()));
}
}
for (_bb_name, block_val) in &blocks {
let bb = block_val.borrow();
if let Some(last_val) = bb.operands.last() {
let last = last_val.borrow();
if last.is_instruction() {
for op_val in &last.operands {
let op = op_val.borrow();
if op.is_basic_block() && !op.name.is_empty() {
if !block_names.contains(&op.name) {
self.error(
format!(
"Block '{}': branch to unknown block '{}'",
bb.name, op.name
),
Some(f.name.clone()),
);
}
}
}
}
}
}
if blocks.is_empty() {
return;
}
let entry_name = &blocks[0].0;
if entry_name.is_empty() {
self.error(
"Function has empty-named entry block".to_string(),
Some(f.name.clone()),
);
}
}
fn verify_dominance(&mut self, func: &ValueRef) {
let dt = DominatorTree::compute(func);
let mut block_map: HashMap<String, usize> = HashMap::new();
let mut blocks: Vec<ValueRef> = Vec::new();
{
let f = func.borrow();
for (i, op) in f.operands.iter().enumerate() {
let bb = op.borrow();
if bb.is_basic_block() {
block_map.insert(bb.name.clone(), i);
blocks.push(op.clone());
}
}
}
if blocks.is_empty() {
return;
}
let mut def_map: HashMap<u64, usize> = HashMap::new();
for (block_idx, block_val) in blocks.iter().enumerate() {
let bb = block_val.borrow();
for inst_val in &bb.operands {
let inst = inst_val.borrow();
if inst.is_instruction() && inst.get_opcode() != Some(Opcode::Phi) {
def_map.insert(inst.vid, block_idx);
}
}
}
for (block_idx, block_val) in blocks.iter().enumerate() {
let bb = block_val.borrow();
for inst_val in &bb.operands {
let inst = inst_val.borrow();
if !inst.is_instruction() || inst.get_opcode() == Some(Opcode::Phi) {
continue;
}
for op_val in &inst.operands {
let op = op_val.borrow();
if op.is_constant() || op.is_function() || op.is_basic_block() {
continue;
}
if let Some(&def_block) = def_map.get(&op.vid) {
if def_block != block_idx
&& !dt.dominates(def_block, block_idx)
&& def_block != block_idx
{
self.error(
format!(
"SSA violation: use of '%{}' in block '{}' is not dominated by definition in block '{}'",
op.name,
bb.name,
blocks[def_block].borrow().name
),
Some(bb.name.clone()),
);
}
}
}
}
}
}
fn verify_reachability(&mut self, func: &ValueRef) {
let f = func.borrow();
let mut blocks: Vec<ValueRef> = Vec::new();
let mut block_names: Vec<String> = Vec::new();
for op in &f.operands {
let bb = op.borrow();
if bb.is_basic_block() {
blocks.push(op.clone());
block_names.push(bb.name.clone());
}
}
if blocks.is_empty() {
return;
}
let mut visited: HashSet<usize> = HashSet::new();
let mut stack: Vec<usize> = vec![0];
while let Some(current) = stack.pop() {
if !visited.insert(current) {
continue;
}
let bb = blocks[current].borrow();
if let Some(last_val) = bb.operands.last() {
let last = last_val.borrow();
if last.is_instruction() {
for op_val in &last.operands {
let op = op_val.borrow();
if op.is_basic_block() {
for (idx, name) in block_names.iter().enumerate() {
if *name == op.name {
stack.push(idx);
break;
}
}
}
}
}
}
}
for idx in 1..blocks.len() {
if !visited.contains(&idx) {
self.warn(format!(
"Block '{}' is unreachable from entry",
block_names[idx]
));
}
}
}
fn verify_type_exists(&mut self, _ty: &Type, _context: &str) {
}
fn verify_not_void_type(&mut self, ty: &Type, context: &str) {
if ty.is_void() {
self.error(
format!("{}: void type not allowed here", context),
Some(context.to_string()),
);
}
}
fn verify_global_variable(&mut self, gv: &ValueRef) {
let g = gv.borrow();
let name = g.name.clone();
if !g.ty.is_pointer() {
self.error(
format!("Global '{}' must have a pointer type", name),
Some(name.clone()),
);
}
self.verify_global_initializer(gv);
}
fn verify_global_initializer(&mut self, gv: &ValueRef) {
let g = gv.borrow();
if g.operands.len() > 0 {
let init = g.operands[0].borrow();
let ptr_ty = &g.ty;
if let TypeKind::Pointer { .. } = &ptr_ty.kind {
}
}
}
fn verify_attributes(&mut self, _func: &ValueRef) {
}
fn verify_param_attrs_compatible(&mut self, _func: &ValueRef) {
}
fn error(&mut self, message: String, context: Option<String>) {
self.errors.push(VerifierError { message, context });
}
fn warn(&mut self, message: String) {
self.warnings.push(VerifierWarning { message });
}
}
impl Default for ModuleVerifier {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum VerifierErrorCode {
NoEntryBlock(String),
DuplicateBlockName(String),
BlockNoTerminator(String),
BlockMultipleTerminators(String),
PhiAfterNonPhi(String),
DuplicateFunctionName(String),
DuplicateGlobalName(String),
EmptyFunctionName,
EmptyGlobalName,
TypeMismatch(String),
BadResultType(String),
InvalidReturnType(String),
VoidTypeNotAllowed(String),
TokenTypeNotAllowed(String),
MetadataTypeNotAllowed(String),
ExpectedPointerType(String),
ExpectedIntegerType(String),
ExpectedFloatingPointType(String),
ExpectedAggregateType(String),
ExpectedVectorType(String),
ExpectedFirstClassType(String),
BadOperandCount(String),
PhiOddOperands(String),
SwitchTooFewOperands(String),
CallMissingCallee(String),
LoadBadOperandCount(String),
StoreBadOperandCount(String),
GEPNoOperands(String),
ExtractValueTooFewOperands(String),
InsertValueTooFewOperands(String),
BrTargetNotABlock(String),
BrToUndefinedBlock(String),
SwitchDefaultNotABlock(String),
SwitchCaseNotABlock(String),
SwitchDuplicateCase(String),
UnreachableBlock(String),
MultipleEntryBlocks(String),
UseNotDominatedByDef(String),
SelfReferentialInstruction(String),
PhiIncomingNotPred(String),
PhiIncomingCountMismatch(String),
AllocaResultNotPointer(String),
LoadOperandNotPointer(String),
LoadResultVoid(String),
LoadAtomicNoAlignment(String),
StorePointerNotPointer(String),
GEPBaseNotPointer(String),
GEPIndexNotInteger(String),
CallCalleeNotCallable(String),
CallArgCountMismatch(String),
CallArgTypeMismatch(String),
RetTypeMismatch(String),
RetValueInVoidFunc(String),
InsertValueTypeMismatch(String),
ShuffleVectorBadMask(String),
CmpXchgPointerNotPointer(String),
AtomicRMWPointerNotPointer(String),
FenceBadOrdering(String),
LandingPadResultNotStruct(String),
LandingPadBadClause(String),
ResumeTypeMismatch(String),
CatchPadNotInCatchSwitch(String),
CatchSwitchNoHandlers(String),
CatchRetBadTarget(String),
CleanupReturnBadUnwind(String),
InvokeNormalNotABlock(String),
InvokeUnwindNotABlock(String),
UndefinedType(String),
DuplicateComdat(String),
InvalidComdatSelection(String),
AliasNotPointer(String),
AliasInvalidAliasee(String),
IFuncResolverNotFunction(String),
InvalidModuleFlagBehavior(String),
BadGlobalCtorDtor(String),
GlobalInitializerTypeMismatch(String),
}
impl VerifierErrorCode {
pub fn message(&self) -> &str {
match self {
Self::NoEntryBlock(_) => "function has no entry basic block",
Self::DuplicateBlockName(_) => "duplicate basic block name",
Self::BlockNoTerminator(_) => "basic block has no terminator",
Self::BlockMultipleTerminators(_) => "basic block has multiple terminators",
Self::PhiAfterNonPhi(_) => "phi node appears after non-phi instruction",
Self::DuplicateFunctionName(_) => "duplicate function name in module",
Self::DuplicateGlobalName(_) => "duplicate global name in module",
Self::EmptyFunctionName => "function has an empty name",
Self::EmptyGlobalName => "global variable has an empty name",
Self::TypeMismatch(_) => "operand type mismatch",
Self::BadResultType(_) => "instruction has invalid result type",
Self::InvalidReturnType(_) => "function return type is invalid",
Self::VoidTypeNotAllowed(_) => "void type not allowed in this context",
Self::TokenTypeNotAllowed(_) => "token type not allowed in this context",
Self::MetadataTypeNotAllowed(_) => "metadata type not allowed as a value",
Self::ExpectedPointerType(_) => "expected pointer type",
Self::ExpectedIntegerType(_) => "expected integer type",
Self::ExpectedFloatingPointType(_) => "expected floating-point type",
Self::ExpectedAggregateType(_) => "expected aggregate type (struct or array)",
Self::ExpectedVectorType(_) => "expected vector type",
Self::ExpectedFirstClassType(_) => "expected first-class type",
Self::BadOperandCount(_) => "wrong number of operands",
Self::PhiOddOperands(_) => "phi node has odd number of operands",
Self::SwitchTooFewOperands(_) => "switch has fewer than 2 operands",
Self::CallMissingCallee(_) => "call instruction has no callee",
Self::LoadBadOperandCount(_) => "load has wrong number of operands",
Self::StoreBadOperandCount(_) => "store has wrong number of operands",
Self::GEPNoOperands(_) => "getelementptr has no operands",
Self::ExtractValueTooFewOperands(_) => "extractvalue has too few operands",
Self::InsertValueTooFewOperands(_) => "insertvalue has too few operands",
Self::BrTargetNotABlock(_) => "branch target is not a basic block",
Self::BrToUndefinedBlock(_) => "branch target block does not exist",
Self::SwitchDefaultNotABlock(_) => "switch default destination is not a basic block",
Self::SwitchCaseNotABlock(_) => "switch case destination is not a basic block",
Self::SwitchDuplicateCase(_) => "switch has duplicate case value",
Self::UnreachableBlock(_) => "basic block is unreachable from entry",
Self::MultipleEntryBlocks(_) => "function has multiple entry blocks",
Self::UseNotDominatedByDef(_) => "use of value is not dominated by its definition",
Self::SelfReferentialInstruction(_) => "instruction references its own result",
Self::PhiIncomingNotPred(_) => "phi incoming block is not a predecessor",
Self::PhiIncomingCountMismatch(_) => "phi incoming count does not match predecessors",
Self::AllocaResultNotPointer(_) => "alloca result must be a pointer type",
Self::LoadOperandNotPointer(_) => "load operand must be a pointer",
Self::LoadResultVoid(_) => "load result type cannot be void",
Self::LoadAtomicNoAlignment(_) => "atomic load requires explicit alignment",
Self::StorePointerNotPointer(_) => "store pointer operand must be a pointer",
Self::GEPBaseNotPointer(_) => "gep base operand must be a pointer",
Self::GEPIndexNotInteger(_) => "gep index operand must be an integer",
Self::CallCalleeNotCallable(_) => "call callee is not callable",
Self::CallArgCountMismatch(_) => "call argument count mismatch",
Self::CallArgTypeMismatch(_) => "call argument type does not match parameter",
Self::RetTypeMismatch(_) => "return type does not match function return type",
Self::RetValueInVoidFunc(_) => "return with value from void function",
Self::InsertValueTypeMismatch(_) => "insertvalue type/subtype mismatch",
Self::ShuffleVectorBadMask(_) => "shufflevector mask element out of range",
Self::CmpXchgPointerNotPointer(_) => "cmpxchg pointer operand must be a pointer",
Self::AtomicRMWPointerNotPointer(_) => "atomicrmw pointer operand must be a pointer",
Self::FenceBadOrdering(_) => "fence has invalid atomic ordering",
Self::LandingPadResultNotStruct(_) => "landingpad result must be a struct",
Self::LandingPadBadClause(_) => "landingpad clause type is invalid",
Self::ResumeTypeMismatch(_) => "resume operand type mismatch",
Self::CatchPadNotInCatchSwitch(_) => "catchpad must be inside a catchswitch",
Self::CatchSwitchNoHandlers(_) => "catchswitch must have at least one handler",
Self::CatchRetBadTarget(_) => "catchret target is not valid",
Self::CleanupReturnBadUnwind(_) => "cleanupreturn unwind destination is invalid",
Self::InvokeNormalNotABlock(_) => "invoke normal destination is not a basic block",
Self::InvokeUnwindNotABlock(_) => "invoke unwind destination is not a basic block",
Self::UndefinedType(_) => "referenced type is not defined",
Self::DuplicateComdat(_) => "duplicate comdat name",
Self::InvalidComdatSelection(_) => "invalid comdat selection kind",
Self::AliasNotPointer(_) => "alias must have pointer type",
Self::AliasInvalidAliasee(_) => "alias target is not a valid global object",
Self::IFuncResolverNotFunction(_) => "ifunc resolver must be a function",
Self::InvalidModuleFlagBehavior(_) => "module flag has invalid behavior",
Self::BadGlobalCtorDtor(_) => "malformed global constructor/destructor entry",
Self::GlobalInitializerTypeMismatch(_) => "global initializer type mismatch",
}
}
pub fn context(&self) -> &str {
match self {
Self::NoEntryBlock(s)
| Self::DuplicateBlockName(s)
| Self::BlockNoTerminator(s)
| Self::BlockMultipleTerminators(s)
| Self::PhiAfterNonPhi(s)
| Self::DuplicateFunctionName(s)
| Self::DuplicateGlobalName(s)
| Self::TypeMismatch(s)
| Self::BadResultType(s)
| Self::InvalidReturnType(s)
| Self::VoidTypeNotAllowed(s)
| Self::TokenTypeNotAllowed(s)
| Self::MetadataTypeNotAllowed(s)
| Self::ExpectedPointerType(s)
| Self::ExpectedIntegerType(s)
| Self::ExpectedFloatingPointType(s)
| Self::ExpectedAggregateType(s)
| Self::ExpectedVectorType(s)
| Self::ExpectedFirstClassType(s)
| Self::BadOperandCount(s)
| Self::PhiOddOperands(s)
| Self::SwitchTooFewOperands(s)
| Self::CallMissingCallee(s)
| Self::LoadBadOperandCount(s)
| Self::StoreBadOperandCount(s)
| Self::GEPNoOperands(s)
| Self::ExtractValueTooFewOperands(s)
| Self::InsertValueTooFewOperands(s)
| Self::BrTargetNotABlock(s)
| Self::BrToUndefinedBlock(s)
| Self::SwitchDefaultNotABlock(s)
| Self::SwitchCaseNotABlock(s)
| Self::SwitchDuplicateCase(s)
| Self::UnreachableBlock(s)
| Self::MultipleEntryBlocks(s)
| Self::UseNotDominatedByDef(s)
| Self::SelfReferentialInstruction(s)
| Self::PhiIncomingNotPred(s)
| Self::PhiIncomingCountMismatch(s)
| Self::AllocaResultNotPointer(s)
| Self::LoadOperandNotPointer(s)
| Self::LoadResultVoid(s)
| Self::LoadAtomicNoAlignment(s)
| Self::StorePointerNotPointer(s)
| Self::GEPBaseNotPointer(s)
| Self::GEPIndexNotInteger(s)
| Self::CallCalleeNotCallable(s)
| Self::CallArgCountMismatch(s)
| Self::CallArgTypeMismatch(s)
| Self::RetTypeMismatch(s)
| Self::RetValueInVoidFunc(s)
| Self::InsertValueTypeMismatch(s)
| Self::ShuffleVectorBadMask(s)
| Self::CmpXchgPointerNotPointer(s)
| Self::AtomicRMWPointerNotPointer(s)
| Self::FenceBadOrdering(s)
| Self::LandingPadResultNotStruct(s)
| Self::LandingPadBadClause(s)
| Self::ResumeTypeMismatch(s)
| Self::CatchPadNotInCatchSwitch(s)
| Self::CatchSwitchNoHandlers(s)
| Self::CatchRetBadTarget(s)
| Self::CleanupReturnBadUnwind(s)
| Self::InvokeNormalNotABlock(s)
| Self::InvokeUnwindNotABlock(s)
| Self::UndefinedType(s)
| Self::DuplicateComdat(s)
| Self::InvalidComdatSelection(s)
| Self::AliasNotPointer(s)
| Self::AliasInvalidAliasee(s)
| Self::IFuncResolverNotFunction(s)
| Self::InvalidModuleFlagBehavior(s)
| Self::BadGlobalCtorDtor(s)
| Self::GlobalInitializerTypeMismatch(s) => s.as_str(),
Self::EmptyFunctionName | Self::EmptyGlobalName => "",
}
}
}
impl std::fmt::Display for VerifierErrorCode {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let ctx = self.context();
if ctx.is_empty() {
write!(f, "{}", self.message())
} else {
write!(f, "{} ({})", self.message(), ctx)
}
}
}
pub fn verify_load_inst(inst: &ValueRef) -> Result<(), VerifierErrorCode> {
let i = inst.borrow();
let name = i.name.clone();
if i.operands.len() != 1 {
return Err(VerifierErrorCode::LoadBadOperandCount(format!(
"{}: expected 1 operand, got {}",
name,
i.operands.len()
)));
}
let ptr_ty = &i.operands[0].borrow().ty;
if !ptr_ty.is_pointer() {
return Err(VerifierErrorCode::LoadOperandNotPointer(format!(
"{}: operand type is {}",
name, ptr_ty
)));
}
if i.ty.is_void() {
return Err(VerifierErrorCode::LoadResultVoid(name.clone()));
}
if i.metadata.contains_key(&1) {
if !i.metadata.contains_key(&2) {
return Err(VerifierErrorCode::LoadAtomicNoAlignment(name.clone()));
}
}
Ok(())
}
pub fn verify_store_inst(inst: &ValueRef) -> Result<(), VerifierErrorCode> {
let i = inst.borrow();
let name = i.name.clone();
if i.operands.len() != 2 {
return Err(VerifierErrorCode::StoreBadOperandCount(format!(
"{}: expected 2 operands, got {}",
name,
i.operands.len()
)));
}
let ptr_ty = &i.operands[1].borrow().ty;
if !ptr_ty.is_pointer() {
return Err(VerifierErrorCode::StorePointerNotPointer(format!(
"{}: pointer operand type is {}",
name, ptr_ty
)));
}
let val_ty = &i.operands[0].borrow().ty;
if val_ty.is_void() {
return Err(VerifierErrorCode::VoidTypeNotAllowed(format!(
"{}: cannot store void value",
name
)));
}
Ok(())
}
pub fn verify_alloca_inst(inst: &ValueRef) -> Result<(), VerifierErrorCode> {
let i = inst.borrow();
let name = i.name.clone();
if !i.ty.is_pointer() {
return Err(VerifierErrorCode::AllocaResultNotPointer(format!(
"{}: result type is {}",
name, i.ty
)));
}
if let Some(size_op) = i.operands.first() {
let size_ty = &size_op.borrow().ty;
if !size_ty.is_integer() {
return Err(VerifierErrorCode::ExpectedIntegerType(format!(
"{}: alloca size operand must be integer, got {}",
name, size_ty
)));
}
}
Ok(())
}
pub fn verify_gep_inst(inst: &ValueRef) -> Result<(), VerifierErrorCode> {
let i = inst.borrow();
let name = i.name.clone();
if i.operands.is_empty() {
return Err(VerifierErrorCode::GEPNoOperands(name.clone()));
}
let base_ty = &i.operands[0].borrow().ty;
if !base_ty.is_pointer() {
return Err(VerifierErrorCode::GEPBaseNotPointer(format!(
"{}: base operand type is {}",
name, base_ty
)));
}
for (idx, op) in i.operands.iter().skip(1).enumerate() {
let op_ty = &op.borrow().ty;
if !op_ty.is_integer() {
return Err(VerifierErrorCode::GEPIndexNotInteger(format!(
"{}: index {} type is {}",
name,
idx + 1,
op_ty
)));
}
}
if !i.ty.is_pointer() {
return Err(VerifierErrorCode::ExpectedPointerType(format!(
"{}: GEP result must be pointer, got {}",
name, i.ty
)));
}
Ok(())
}
pub fn verify_call_inst(inst: &ValueRef) -> Result<(), VerifierErrorCode> {
let i = inst.borrow();
let name = i.name.clone();
if i.operands.is_empty() {
return Err(VerifierErrorCode::CallMissingCallee(name.clone()));
}
let callee = i.operands[0].borrow();
if !callee.is_function() && !callee.ty.is_pointer() {
return Err(VerifierErrorCode::CallCalleeNotCallable(format!(
"{}: callee '{}' is not a function or function pointer",
name, callee.name
)));
}
if callee.is_function() {
if let TypeKind::Function { .. } = &callee.ty.kind {
}
}
Ok(())
}
pub fn verify_phi_inst(inst: &ValueRef) -> Result<(), VerifierErrorCode> {
let i = inst.borrow();
let name = i.name.clone();
let num_ops = i.operands.len();
if num_ops == 0 || num_ops % 2 != 0 {
return Err(VerifierErrorCode::PhiOddOperands(format!(
"{}: phi requires even number of operands, got {}",
name, num_ops
)));
}
let pair_count = num_ops / 2;
if pair_count > 1 {
let first_val_ty = &i.operands[0].borrow().ty;
for p in 1..pair_count {
let val_ty = &i.operands[p * 2].borrow().ty;
if val_ty.kind != first_val_ty.kind {
return Err(VerifierErrorCode::TypeMismatch(format!(
"{}: incoming value types differ ({} vs {})",
name, first_val_ty, val_ty
)));
}
}
}
for p in 0..pair_count {
let label = i.operands[p * 2 + 1].borrow();
if !label.is_basic_block() {
return Err(VerifierErrorCode::BrTargetNotABlock(format!(
"{}: incoming block at position {} is not a basic block",
name, p
)));
}
}
Ok(())
}
pub fn verify_br_inst(inst: &ValueRef) -> Result<(), VerifierErrorCode> {
let i = inst.borrow();
let name = i.name.clone();
let num_ops = i.operands.len();
match num_ops {
1 => {
let target = i.operands[0].borrow();
if !target.is_basic_block() {
return Err(VerifierErrorCode::BrTargetNotABlock(format!(
"{}: target '{}' is not a basic block",
name, target.name
)));
}
}
3 => {
let cond = i.operands[0].borrow();
if !cond.ty.is_integer() || cond.ty.integer_bit_width() != 1 {
}
for (idx, op_idx) in [1usize, 2].iter().enumerate() {
let target = i.operands[*op_idx].borrow();
if !target.is_basic_block() {
return Err(VerifierErrorCode::BrTargetNotABlock(format!(
"{}: branch destination {} ('{}') is not a basic block",
name, idx, target.name
)));
}
}
}
_ => {
return Err(VerifierErrorCode::BadOperandCount(format!(
"{}: br requires 1 or 3 operands, got {}",
name, num_ops
)));
}
}
Ok(())
}
pub fn verify_switch_inst(inst: &ValueRef) -> Result<(), VerifierErrorCode> {
let i = inst.borrow();
let name = i.name.clone();
let num_ops = i.operands.len();
if num_ops < 2 {
return Err(VerifierErrorCode::SwitchTooFewOperands(format!(
"{}: switch requires at least 2 operands, got {}",
name, num_ops
)));
}
let val_ty = &i.operands[0].borrow().ty;
if !val_ty.is_integer() {
return Err(VerifierErrorCode::ExpectedIntegerType(format!(
"{}: switch condition must be integer, got {}",
name, val_ty
)));
}
let default_dest = i.operands[num_ops - 1].borrow();
if !default_dest.is_basic_block() {
return Err(VerifierErrorCode::SwitchDefaultNotABlock(format!(
"{}: default destination '{}' is not a basic block",
name, default_dest.name
)));
}
let pair_count = (num_ops - 2) / 2;
let mut seen_cases: HashSet<String> = HashSet::new();
for p in 0..pair_count {
let case_idx = 1 + p * 2;
let dest_idx = case_idx + 1;
let case_val = i.operands[case_idx].borrow();
let case_dest = i.operands[dest_idx].borrow();
if !case_dest.is_basic_block() {
return Err(VerifierErrorCode::SwitchCaseNotABlock(format!(
"{}: case {} destination is not a basic block",
name, p
)));
}
let case_key = format!("{}_{}", case_val.name, case_val.vid);
if !seen_cases.insert(case_key) {
return Err(VerifierErrorCode::SwitchDuplicateCase(format!(
"{}: duplicate case value '{}'",
name, case_val.name
)));
}
}
Ok(())
}
pub fn verify_ret_inst(
inst: &ValueRef,
func_return_ty: Option<&Type>,
) -> Result<(), VerifierErrorCode> {
let i = inst.borrow();
let name = i.name.clone();
match func_return_ty {
Some(ret_ty) => {
if ret_ty.is_void() {
if !i.operands.is_empty() {
return Err(VerifierErrorCode::RetValueInVoidFunc(format!(
"{}: cannot return a value from void function",
name
)));
}
} else {
if i.operands.len() != 1 {
return Err(VerifierErrorCode::BadOperandCount(format!(
"{}: return requires 1 operand for non-void function, got {}",
name,
i.operands.len()
)));
}
let val_ty = &i.operands[0].borrow().ty;
if val_ty.kind != ret_ty.kind {
return Err(VerifierErrorCode::RetTypeMismatch(format!(
"{}: return type {} does not match function return type {}",
name, val_ty, ret_ty
)));
}
}
}
None => {
if !i.operands.is_empty() && i.operands[0].borrow().ty.is_void() {
return Err(VerifierErrorCode::VoidTypeNotAllowed(format!(
"{}: cannot return void value",
name
)));
}
}
}
Ok(())
}
pub fn verify_unreachable_inst(inst: &ValueRef) -> Result<(), VerifierErrorCode> {
let i = inst.borrow();
if !i.operands.is_empty() {
return Err(VerifierErrorCode::BadOperandCount(format!(
"{}: unreachable must have no operands, got {}",
i.name,
i.operands.len()
)));
}
Ok(())
}
pub fn verify_insert_value_inst(inst: &ValueRef) -> Result<(), VerifierErrorCode> {
let i = inst.borrow();
let name = i.name.clone();
if i.operands.len() < 3 {
return Err(VerifierErrorCode::InsertValueTooFewOperands(format!(
"{}: insertvalue requires at least 3 operands (agg, val, indices...), got {}",
name,
i.operands.len()
)));
}
let agg_ty = &i.operands[0].borrow().ty;
let val_ty = &i.operands[1].borrow().ty;
match &agg_ty.kind {
TypeKind::Struct { .. } | TypeKind::Array { .. } | TypeKind::FixedVector { .. } => {}
_ => {
return Err(VerifierErrorCode::ExpectedAggregateType(format!(
"{}: first operand must be aggregate type, got {}",
name, agg_ty
)));
}
}
if i.ty.kind != agg_ty.kind {
return Err(VerifierErrorCode::InsertValueTypeMismatch(format!(
"{}: result type {} does not match aggregate type {}",
name, i.ty, agg_ty
)));
}
Ok(())
}
pub fn verify_extract_value_inst(inst: &ValueRef) -> Result<(), VerifierErrorCode> {
let i = inst.borrow();
let name = i.name.clone();
if i.operands.len() < 2 {
return Err(VerifierErrorCode::ExtractValueTooFewOperands(format!(
"{}: extractvalue requires at least 2 operands, got {}",
name,
i.operands.len()
)));
}
let agg_ty = &i.operands[0].borrow().ty;
match &agg_ty.kind {
TypeKind::Struct { .. } | TypeKind::Array { .. } | TypeKind::FixedVector { .. } => {}
_ => {
return Err(VerifierErrorCode::ExpectedAggregateType(format!(
"{}: base operand must be aggregate, got {}",
name, agg_ty
)));
}
}
for (idx, op) in i.operands.iter().skip(1).enumerate() {
let op_val = op.borrow();
if !op_val.ty.is_integer() {
return Err(VerifierErrorCode::ExpectedIntegerType(format!(
"{}: index {} must be integer, got {}",
name, idx, op_val.ty
)));
}
}
Ok(())
}
pub fn verify_shuffle_vector_inst(inst: &ValueRef) -> Result<(), VerifierErrorCode> {
let i = inst.borrow();
let name = i.name.clone();
if i.operands.len() != 3 {
return Err(VerifierErrorCode::BadOperandCount(format!(
"{}: shufflevector requires 3 operands, got {}",
name,
i.operands.len()
)));
}
let v1_ty = &i.operands[0].borrow().ty;
let v2_ty = &i.operands[1].borrow().ty;
if v1_ty.kind != v2_ty.kind {
return Err(VerifierErrorCode::TypeMismatch(format!(
"{}: vector operand types differ ({} vs {})",
name, v1_ty, v2_ty
)));
}
let mask_ty = &i.operands[2].borrow().ty;
if !matches!(
mask_ty.kind,
TypeKind::FixedVector { .. } | TypeKind::ScalableVector { .. }
) {
return Err(VerifierErrorCode::ExpectedVectorType(format!(
"{}: mask must be a vector type, got {}",
name, mask_ty
)));
}
if !matches!(
i.ty.kind,
TypeKind::FixedVector { .. } | TypeKind::ScalableVector { .. }
) {
return Err(VerifierErrorCode::ExpectedVectorType(format!(
"{}: result must be a vector type, got {}",
name, i.ty
)));
}
Ok(())
}
pub fn verify_cmpxchg_inst(inst: &ValueRef) -> Result<(), VerifierErrorCode> {
let i = inst.borrow();
let name = i.name.clone();
if i.operands.len() < 3 {
return Err(VerifierErrorCode::BadOperandCount(format!(
"{}: cmpxchg requires at least 3 operands, got {}",
name,
i.operands.len()
)));
}
let ptr_ty = &i.operands[0].borrow().ty;
if !ptr_ty.is_pointer() {
return Err(VerifierErrorCode::CmpXchgPointerNotPointer(format!(
"{}: first operand must be pointer, got {}",
name, ptr_ty
)));
}
Ok(())
}
pub fn verify_atomicrmw_inst(inst: &ValueRef) -> Result<(), VerifierErrorCode> {
let i = inst.borrow();
let name = i.name.clone();
if i.operands.len() < 2 {
return Err(VerifierErrorCode::BadOperandCount(format!(
"{}: atomicrmw requires at least 2 operands, got {}",
name,
i.operands.len()
)));
}
let ptr_ty = &i.operands[0].borrow().ty;
if !ptr_ty.is_pointer() {
return Err(VerifierErrorCode::AtomicRMWPointerNotPointer(format!(
"{}: first operand must be pointer, got {}",
name, ptr_ty
)));
}
Ok(())
}
pub fn verify_fence_inst(inst: &ValueRef) -> Result<(), VerifierErrorCode> {
let i = inst.borrow();
let name = i.name.clone();
if !i.operands.is_empty() {
}
let _ordering = i.subclass_data & 0xF;
if _ordering <= 2 && !i.operands.is_empty() {
return Err(VerifierErrorCode::FenceBadOrdering(format!(
"{}: fence requires at least acquire ordering",
name
)));
}
Ok(())
}
pub fn verify_landingpad_inst(inst: &ValueRef) -> Result<(), VerifierErrorCode> {
let i = inst.borrow();
let name = i.name.clone();
if !matches!(i.ty.kind, TypeKind::Struct { .. }) {
return Err(VerifierErrorCode::LandingPadResultNotStruct(format!(
"{}: landingpad result must be a struct type, got {}",
name, i.ty
)));
}
for (idx, op) in i.operands.iter().enumerate() {
let op_val = op.borrow();
if !op_val.is_constant() {
return Err(VerifierErrorCode::LandingPadBadClause(format!(
"{}: clause {} must be a constant, got {}",
name, idx, op_val.ty
)));
}
}
Ok(())
}
pub fn verify_resume_inst(inst: &ValueRef) -> Result<(), VerifierErrorCode> {
let i = inst.borrow();
let name = i.name.clone();
if i.operands.len() != 1 {
return Err(VerifierErrorCode::BadOperandCount(format!(
"{}: resume requires exactly 1 operand, got {}",
name,
i.operands.len()
)));
}
Ok(())
}
pub fn verify_catchpad_inst(inst: &ValueRef) -> Result<(), VerifierErrorCode> {
let i = inst.borrow();
let name = i.name.clone();
if i.operands.is_empty() {
return Err(VerifierErrorCode::BadOperandCount(format!(
"{}: catchpad requires at least 1 operand, got 0",
name
)));
}
Ok(())
}
pub fn verify_cleanuppad_inst(inst: &ValueRef) -> Result<(), VerifierErrorCode> {
let i = inst.borrow();
let name = i.name.clone();
if i.operands.is_empty() {
return Err(VerifierErrorCode::BadOperandCount(format!(
"{}: cleanuppad requires at least 1 operand, got 0",
name
)));
}
Ok(())
}
pub fn verify_catchswitch_inst(inst: &ValueRef) -> Result<(), VerifierErrorCode> {
let i = inst.borrow();
let name = i.name.clone();
let has_handler = i.operands.iter().any(|op| op.borrow().is_basic_block());
if !has_handler && !i.operands.is_empty() {
return Err(VerifierErrorCode::CatchSwitchNoHandlers(format!(
"{}: catchswitch must have at least one handler block",
name
)));
}
Ok(())
}
pub fn verify_catchret_inst(inst: &ValueRef) -> Result<(), VerifierErrorCode> {
let i = inst.borrow();
let name = i.name.clone();
if i.operands.len() < 2 {
return Err(VerifierErrorCode::BadOperandCount(format!(
"{}: catchret requires at least 2 operands, got {}",
name,
i.operands.len()
)));
}
let target = i.operands[1].borrow();
if !target.is_basic_block() {
return Err(VerifierErrorCode::CatchRetBadTarget(format!(
"{}: catchret target must be a basic block",
name
)));
}
Ok(())
}
pub fn verify_cleanupret_inst(inst: &ValueRef) -> Result<(), VerifierErrorCode> {
let i = inst.borrow();
let name = i.name.clone();
if i.operands.is_empty() {
return Err(VerifierErrorCode::BadOperandCount(format!(
"{}: cleanupret requires at least 1 operand, got 0",
name
)));
}
if i.operands.len() >= 2 {
let unwind = i.operands[1].borrow();
if !unwind.is_basic_block() {
return Err(VerifierErrorCode::CleanupReturnBadUnwind(format!(
"{}: unwind destination must be a basic block",
name
)));
}
}
Ok(())
}
pub fn verify_function_deep(func: &ValueRef) -> VerifierResult {
let f = func.borrow();
let func_name = f.name.clone();
let mut result = VerifierResult::success();
result.functions_verified = 1;
if f.operands.is_empty() {
return result;
}
let has_entry = f
.operands
.first()
.map(|op| op.borrow().is_basic_block())
.unwrap_or(false);
if !has_entry {
result.error(format!(
"Function '{}': must have an entry basic block",
func_name
));
return result;
}
let mut blocks: Vec<ValueRef> = Vec::new();
let mut block_names: HashSet<String> = HashSet::new();
for op in &f.operands {
let bb = op.borrow();
if bb.is_basic_block() {
if !block_names.insert(bb.name.clone()) {
result.warn(format!(
"Function '{}': duplicate basic block name '{}'",
func_name, bb.name
));
}
blocks.push(op.clone());
result.instructions_verified += bb.operands.len();
}
}
if blocks.is_empty() {
result.error(format!("Function '{}': has no basic blocks", func_name));
return result;
}
for block_val in &blocks {
let bb = block_val.borrow();
let has_terminator = bb
.operands
.last()
.map(|last| {
let l = last.borrow();
l.is_instruction() && l.get_opcode().map(|op| op.is_terminator()).unwrap_or(false)
})
.unwrap_or(false);
if !has_terminator {
result.error(format!("Block '{}': no terminator instruction", bb.name));
}
let mut seen_non_phi = false;
for inst_val in &bb.operands {
let inst = inst_val.borrow();
if !inst.is_instruction() {
continue;
}
let is_phi = inst.get_opcode() == Some(Opcode::Phi);
if is_phi && seen_non_phi {
result.error(format!(
"Block '{}': phi node appears after non-phi instruction",
bb.name
));
}
if !is_phi {
seen_non_phi = true;
}
}
}
let dt = DominatorTree::compute(func);
let mut block_idx_map: HashMap<String, usize> = HashMap::new();
for (i, block_val) in blocks.iter().enumerate() {
block_idx_map.insert(block_val.borrow().name.clone(), i);
}
let mut def_map: HashMap<u64, usize> = HashMap::new();
for (block_idx, block_val) in blocks.iter().enumerate() {
let bb = block_val.borrow();
for inst_val in &bb.operands {
let inst = inst_val.borrow();
if inst.is_instruction() && inst.get_opcode() != Some(Opcode::Phi) {
def_map.insert(inst.vid, block_idx);
}
}
}
for (block_idx, block_val) in blocks.iter().enumerate() {
let bb = block_val.borrow();
for inst_val in &bb.operands {
let inst = inst_val.borrow();
if !inst.is_instruction() || inst.get_opcode() == Some(Opcode::Phi) {
continue;
}
for op_val in &inst.operands {
let op = op_val.borrow();
if op.is_constant() || op.is_function() || op.is_basic_block() {
continue;
}
if let Some(&def_block) = def_map.get(&op.vid) {
if def_block != block_idx && !dt.dominates(def_block, block_idx) {
result.error(format!(
"SSA violation: use of '%{}' in block '{}' not dominated by definition in '{}'",
op.name, bb.name, blocks[def_block].borrow().name
));
}
}
}
}
}
let mut visited: HashSet<usize> = HashSet::new();
let mut stack: Vec<usize> = vec![0];
while let Some(current) = stack.pop() {
if !visited.insert(current) {
continue;
}
if let Some(block_val) = blocks.get(current) {
let bb = block_val.borrow();
if let Some(last_val) = bb.operands.last() {
let last = last_val.borrow();
if last.is_instruction() {
for op_val in &last.operands {
let op = op_val.borrow();
if op.is_basic_block() {
for (idx, block_val2) in blocks.iter().enumerate() {
if block_val2.borrow().name == op.name {
stack.push(idx);
break;
}
}
}
}
}
}
}
}
for idx in 1..blocks.len() {
if !visited.contains(&idx) {
result.warn(format!(
"Block '{}' is unreachable",
blocks[idx].borrow().name
));
}
}
result
}
pub fn verify_dominance_full(func: &ValueRef) -> VerifierResult {
let mut result = VerifierResult::success();
let dt = DominatorTree::compute(func);
let mut blocks: Vec<ValueRef> = Vec::new();
let mut block_map: HashMap<String, usize> = HashMap::new();
{
let f = func.borrow();
for (i, op) in f.operands.iter().enumerate() {
let bb = op.borrow();
if bb.is_basic_block() {
block_map.insert(bb.name.clone(), i);
blocks.push(op.clone());
}
}
}
if blocks.is_empty() {
return result;
}
let mut def_map: HashMap<u64, usize> = HashMap::new();
for (block_idx, block_val) in blocks.iter().enumerate() {
let bb = block_val.borrow();
for inst_val in &bb.operands {
let inst = inst_val.borrow();
if inst.is_instruction() && inst.get_opcode() != Some(Opcode::Phi) {
def_map.insert(inst.vid, block_idx);
}
}
}
for (block_idx, block_val) in blocks.iter().enumerate() {
let bb = block_val.borrow();
for inst_val in &bb.operands {
let inst = inst_val.borrow();
if !inst.is_instruction() || inst.get_opcode() == Some(Opcode::Phi) {
continue;
}
for op_val in &inst.operands {
if Rc::ptr_eq(op_val, inst_val) {
result.error(format!("Instruction '%{}' references itself", inst.name));
continue;
}
let op = op_val.borrow();
if op.is_constant() || op.is_function() || op.is_basic_block() {
continue;
}
if let Some(&def_block) = def_map.get(&op.vid) {
if def_block != block_idx && !dt.dominates(def_block, block_idx) {
result.error(format!(
"Use of '%{}' in block '{}' not dominated by definition in '{}'",
op.name,
bb.name,
blocks[def_block].borrow().name
));
}
}
}
}
}
result
}
pub fn verify_loop_info(func: &ValueRef) -> VerifierResult {
let mut result = VerifierResult::success();
let f = func.borrow();
let mut blocks: Vec<ValueRef> = Vec::new();
let mut name_to_idx: HashMap<String, usize> = HashMap::new();
for (i, op) in f.operands.iter().enumerate() {
let bb = op.borrow();
if bb.is_basic_block() {
name_to_idx.insert(bb.name.clone(), i);
blocks.push(op.clone());
}
}
if blocks.len() < 2 {
return result; }
let dt = DominatorTree::compute(func);
for (src_idx, src_val) in blocks.iter().enumerate() {
let src = src_val.borrow();
if let Some(last_val) = src.operands.last() {
let last = last_val.borrow();
if last.is_instruction() {
for op_val in &last.operands {
let op = op_val.borrow();
if op.is_basic_block() {
if let Some(&dst_idx) = name_to_idx.get(&op.name) {
if dst_idx != src_idx && dt.dominates(dst_idx, src_idx) {
let _header_name = &op.name;
}
}
}
}
}
}
}
result
}
pub fn verify_type_resolution(module: &crate::module::Module) -> VerifierResult {
let mut result = VerifierResult::success();
let defined_types: HashSet<String> = module.named_types.keys().cloned().collect();
for func in &module.functions {
let f = func.borrow();
if let TypeKind::Struct { name, .. } = &f.ty.kind {
if let Some(type_name) = name {
if !type_name.is_empty() && !defined_types.contains(type_name) {
result.warn(format!(
"Function '{}': return type references undefined struct '%{}'",
f.name, type_name
));
}
}
}
for op in &f.operands {
let bb = op.borrow();
if bb.is_basic_block() {
for inst_val in &bb.operands {
let inst = inst_val.borrow();
if let TypeKind::Struct { name, .. } = &inst.ty.kind {
if let Some(type_name) = name {
if !type_name.is_empty() && !defined_types.contains(type_name) {
result.warn(format!(
"Instruction '%{}': type references undefined struct '%{}'",
inst.name, type_name
));
}
}
}
}
}
}
}
result
}
pub fn verify_comdat(module: &crate::module::Module) -> VerifierResult {
let mut result = VerifierResult::success();
for (_name, comdat) in &module.comdats {
use crate::module::ComdatKind;
match comdat.kind {
ComdatKind::Any
| ComdatKind::ExactMatch
| ComdatKind::Largest
| ComdatKind::NoDeduplicate
| ComdatKind::SameSize => {}
}
}
result
}
pub fn verify_alias(alias: &ValueRef) -> VerifierResult {
let mut result = VerifierResult::success();
let a = alias.borrow();
let name = a.name.clone();
if !a.ty.is_pointer() {
result.error(format!(
"Alias '{}': must have pointer type, got {}",
name, a.ty
));
}
if a.operands.len() != 1 {
result.error(format!(
"Alias '{}': must have exactly 1 operand (aliasee), got {}",
name,
a.operands.len()
));
} else {
let aliasee = a.operands[0].borrow();
if !aliasee.is_function()
&& !aliasee.is_global_variable()
&& aliasee.subclass != SubclassKind::GlobalAlias
{
result.warn(format!(
"Alias '{}': aliasee '{}' may not be a valid global object",
name, aliasee.name
));
}
}
result
}
pub fn verify_ifunc(ifunc: &ValueRef) -> VerifierResult {
let mut result = VerifierResult::success();
let i = ifunc.borrow();
let name = i.name.clone();
if !i.ty.is_pointer() {
result.error(format!(
"IFunc '{}': must have pointer type, got {}",
name, i.ty
));
}
if let Some(resolver) = i.operands.first() {
let r = resolver.borrow();
if !r.is_function() {
result.error(format!(
"IFunc '{}': resolver '{}' must be a function",
name, r.name
));
}
}
result
}
pub fn verify_module_flags(module: &crate::module::Module) -> VerifierResult {
let mut result = VerifierResult::success();
for flag in &module.flags {
if flag.behavior < 1 || flag.behavior > 8 {
result.warn(format!(
"Module flag '{}': unrecognized behavior value {}",
flag.key, flag.behavior
));
}
}
result
}
pub fn verify_global_ctors_dtors(module: &crate::module::Module) -> VerifierResult {
let mut result = VerifierResult::success();
if let Some(ctor_md_ids) = module.named_metadata.get("llvm.global_ctors") {
for (idx, _md_id) in ctor_md_ids.iter().enumerate() {
if *_md_id == 0 {
result.error(format!("Global constructor #{}: null metadata ID", idx));
}
}
}
if let Some(dtor_md_ids) = module.named_metadata.get("llvm.global_dtors") {
for (idx, _md_id) in dtor_md_ids.iter().enumerate() {
if *_md_id == 0 {
result.error(format!("Global destructor #{}: null metadata ID", idx));
}
}
}
result
}
impl ModuleVerifier {
pub fn verify_instruction_with_codes(
&mut self,
inst: &ValueRef,
bb: &ValueRef,
) -> Vec<VerifierErrorCode> {
let mut codes = Vec::new();
let i = inst.borrow();
if !i.is_instruction() {
return codes;
}
match i.get_opcode() {
Some(Opcode::Load) => {
if let Err(e) = verify_load_inst(inst) {
codes.push(e);
}
}
Some(Opcode::Store) => {
if let Err(e) = verify_store_inst(inst) {
codes.push(e);
}
}
Some(Opcode::Alloca) => {
if let Err(e) = verify_alloca_inst(inst) {
codes.push(e);
}
}
Some(Opcode::GetElementPtr) => {
if let Err(e) = verify_gep_inst(inst) {
codes.push(e);
}
}
Some(Opcode::Call) => {
if let Err(e) = verify_call_inst(inst) {
codes.push(e);
}
}
Some(Opcode::Phi) => {
if let Err(e) = verify_phi_inst(inst) {
codes.push(e);
}
}
Some(Opcode::Br) => {
if let Err(e) = verify_br_inst(inst) {
codes.push(e);
}
}
Some(Opcode::Switch) => {
if let Err(e) = verify_switch_inst(inst) {
codes.push(e);
}
}
Some(Opcode::Ret) => {
if let Err(e) = verify_ret_inst(inst, None) {
codes.push(e);
}
}
Some(Opcode::Unreachable) => {
if let Err(e) = verify_unreachable_inst(inst) {
codes.push(e);
}
}
Some(Opcode::InsertValue) => {
if let Err(e) = verify_insert_value_inst(inst) {
codes.push(e);
}
}
Some(Opcode::ExtractValue) => {
if let Err(e) = verify_extract_value_inst(inst) {
codes.push(e);
}
}
Some(Opcode::ShuffleVector) => {
if let Err(e) = verify_shuffle_vector_inst(inst) {
codes.push(e);
}
}
Some(Opcode::CmpXchg) => {
if let Err(e) = verify_cmpxchg_inst(inst) {
codes.push(e);
}
}
Some(Opcode::AtomicRMW) => {
if let Err(e) = verify_atomicrmw_inst(inst) {
codes.push(e);
}
}
Some(Opcode::Fence) => {
if let Err(e) = verify_fence_inst(inst) {
codes.push(e);
}
}
Some(Opcode::LandingPad) => {
if let Err(e) = verify_landingpad_inst(inst) {
codes.push(e);
}
}
Some(Opcode::Resume) => {
if let Err(e) = verify_resume_inst(inst) {
codes.push(e);
}
}
Some(Opcode::CatchPad) => {
if let Err(e) = verify_catchpad_inst(inst) {
codes.push(e);
}
}
Some(Opcode::CleanupPad) => {
if let Err(e) = verify_cleanuppad_inst(inst) {
codes.push(e);
}
}
Some(Opcode::CatchSwitch) => {
if let Err(e) = verify_catchswitch_inst(inst) {
codes.push(e);
}
}
Some(Opcode::CatchRet) => {
if let Err(e) = verify_catchret_inst(inst) {
codes.push(e);
}
}
Some(Opcode::CleanupRet) => {
if let Err(e) = verify_cleanupret_inst(inst) {
codes.push(e);
}
}
_ => {}
}
codes
}
pub fn verify_module_with_codes(
module: &crate::module::Module,
) -> (Vec<VerifierErrorCode>, Vec<String>) {
let mut codes: Vec<VerifierErrorCode> = Vec::new();
let mut warnings: Vec<String> = Vec::new();
let type_result = verify_type_resolution(module);
warnings.extend(type_result.warnings);
let comdat_result = verify_comdat(module);
for e in &comdat_result.errors {
codes.push(VerifierErrorCode::DuplicateComdat(e.clone()));
}
warnings.extend(comdat_result.warnings);
let flags_result = verify_module_flags(module);
warnings.extend(flags_result.warnings);
let ctors_result = verify_global_ctors_dtors(module);
for e in &ctors_result.errors {
codes.push(VerifierErrorCode::BadGlobalCtorDtor(e.clone()));
}
warnings.extend(ctors_result.warnings);
for func in &module.functions {
let func_result = verify_function_full(func);
warnings.extend(func_result.warnings);
for e in &func_result.errors {
if e.contains("not dominated") {
codes.push(VerifierErrorCode::UseNotDominatedByDef(e.clone()));
} else if e.contains("references itself") {
codes.push(VerifierErrorCode::SelfReferentialInstruction(e.clone()));
} else if e.contains("no terminator") {
codes.push(VerifierErrorCode::BlockNoTerminator(e.clone()));
} else if e.contains("phi node appears after") {
codes.push(VerifierErrorCode::PhiAfterNonPhi(e.clone()));
} else {
codes.push(VerifierErrorCode::TypeMismatch(e.clone()));
}
}
}
for gv in &module.globals {
let g = gv.borrow();
match g.subclass {
SubclassKind::GlobalAlias => {
let alias_result = verify_alias(gv);
for e in &alias_result.errors {
codes.push(VerifierErrorCode::AliasNotPointer(e.clone()));
}
warnings.extend(alias_result.warnings);
}
SubclassKind::GlobalIFunc => {
let ifunc_result = verify_ifunc(gv);
for e in &ifunc_result.errors {
codes.push(VerifierErrorCode::IFuncResolverNotFunction(e.clone()));
}
warnings.extend(ifunc_result.warnings);
}
_ => {}
}
}
(codes, warnings)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::basic_block;
use crate::constants;
use crate::context::LLVMContext;
use crate::function;
use crate::instruction;
fn build_simple_func() -> ValueRef {
let mut ctx = LLVMContext::new();
let func = function::new_function("test", ctx.void_ty(), &[]);
let entry = basic_block::new_basic_block("entry");
let ret = instruction::ret_void();
entry.borrow_mut().push_operand(ret);
func.borrow_mut().push_operand(entry);
func
}
#[test]
fn test_verify_valid_function() {
let func = build_simple_func();
let result = verify_function(&func);
assert!(
result.is_valid,
"Valid function should pass: {:?}",
result.errors
);
}
#[test]
fn test_verify_module_basic() {
let mut ctx = LLVMContext::new();
let mut module = crate::module::Module::new("test");
module.set_target_triple("x86_64-unknown-linux-gnu");
let func = build_simple_func();
module.add_function(func);
let result = verify_module(&module);
assert!(
result.is_valid || result.errors.iter().any(|e| e.contains("dominate")),
"Module verification should complete without panicking"
);
}
#[test]
fn test_verify_block_no_terminator() {
let bb = basic_block::new_basic_block("bad");
let result = verify_basic_block(&bb);
assert!(!result.is_valid, "Block without terminator should fail");
}
#[test]
fn test_verify_instruction_alloca() {
let alloca = instruction::alloca(Type::i32());
let result = verify_instruction(&alloca);
assert!(result.is_valid, "Alloca should be valid");
}
#[test]
fn test_verify_instruction_br_bad() {
let bad_br = instruction::br(constants::const_i32(0));
let result = verify_instruction(&bad_br);
assert!(!result.is_valid, "Branch to non-block should fail");
}
#[test]
fn test_verify_instruction_icmp() {
let a = constants::const_i32(5);
let b = constants::const_i32(10);
let icmp = instruction::icmp(crate::opcode::ICmpPred::Eq, a, b);
let result = verify_instruction(&icmp);
assert!(
result.is_valid,
"Valid icmp should pass: {:?}",
result.errors
);
}
#[test]
fn test_verify_instruction_store() {
let val = constants::const_i32(42);
let ptr = instruction::alloca(Type::i32());
let store = instruction::store(val, ptr);
let result = verify_instruction(&store);
assert!(
result.is_valid,
"Valid store should pass: {:?}",
result.errors
);
}
#[test]
fn test_verify_dominance_simple() {
let func = build_simple_func();
let result = verify_dominance(&func);
assert!(
result.is_valid,
"Simple function should pass dominance: {:?}",
result.errors
);
}
#[test]
fn test_verify_duplicate_function_names() {
let mut ctx = LLVMContext::new();
let mut module = crate::module::Module::new("test");
module.set_target_triple("x86_64");
let f1 = function::new_function("dup", ctx.void_ty(), &[]);
let f2 = function::new_function("dup", ctx.void_ty(), &[]);
module.add_function_unchecked(f1);
module.add_function_unchecked(f2);
let result = verify_module(&module);
assert!(!result.is_valid, "Duplicate function names should fail");
}
#[test]
fn test_verify_function_no_body() {
let mut ctx = LLVMContext::new();
let func = function::new_function("decl", ctx.i32(), &[]);
let result = verify_function(&func);
assert!(result.is_valid, "Declaration should pass");
}
#[test]
fn test_verify_warns_no_triple() {
let mut ctx = LLVMContext::new();
let mut module = crate::module::Module::new("test");
let func = build_simple_func();
module.add_function(func);
let result = verify_module(&module);
assert!(
!result.warnings.is_empty(),
"Should warn about missing triple"
);
}
#[test]
fn test_verify_phi_valid() {
let mut ctx = LLVMContext::new();
let func = function::new_function("test", ctx.i32(), &[]);
let entry = basic_block::new_basic_block("entry");
let branch = basic_block::new_basic_block("branch");
let phi = instruction::phi(
ctx.i32(),
vec![
(constants::const_i32(0), entry.clone()),
(constants::const_i32(1), branch.clone()),
],
);
entry.borrow_mut().push_operand(phi);
entry
.borrow_mut()
.push_operand(instruction::br(branch.clone()));
branch.borrow_mut().push_operand(instruction::ret_void());
func.borrow_mut().push_operand(entry);
func.borrow_mut().push_operand(branch);
let result = verify_function(&func);
assert!(
!result.is_valid || result.is_valid,
"Phi test should not panic"
);
}
#[test]
fn test_verifier_pass_trait() {
let mut pass = VerifierPass;
assert_eq!(pass.name(), "verify");
}
#[test]
fn test_module_verifier_pass_trait() {
let mut pass = ModuleVerifierPass;
assert_eq!(pass.name(), "module-verify");
}
#[test]
fn test_module_verifier_create() {
let v = ModuleVerifier::new();
assert!(v.errors.is_empty());
assert!(v.warnings.is_empty());
}
#[test]
fn test_module_verifier_default() {
let v = ModuleVerifier::default();
assert!(v.errors.is_empty());
}
#[test]
fn test_module_verifier_empty_module() {
let module = crate::module::Module::new("empty");
let result = ModuleVerifier::verify_module(&module);
assert!(result.is_ok());
}
#[test]
fn test_module_verifier_duplicate_functions() {
let mut ctx = LLVMContext::new();
let mut module = crate::module::Module::new("dup_mod");
module.set_target_triple("x86_64");
let f1 = function::new_function("dup", ctx.void_ty(), &[]);
let f2 = function::new_function("dup", ctx.void_ty(), &[]);
module.add_function_unchecked(f1);
module.add_function_unchecked(f2);
let result = ModuleVerifier::verify_module(&module);
assert!(result.is_err());
}
#[test]
fn test_module_verifier_no_triple_warns() {
let module = crate::module::Module::new("no_triple");
let mut v = ModuleVerifier::new();
v.verify_target_triple(&module);
assert!(!v.warnings.is_empty());
}
#[test]
fn test_module_verifier_no_data_layout_warns() {
let module = crate::module::Module::new("no_dl");
let mut v = ModuleVerifier::new();
v.verify_data_layout(&module);
assert!(!v.warnings.is_empty());
}
#[test]
fn test_module_verifier_valid_function() {
let mut module = crate::module::Module::new("valid_mod");
module.set_target_triple("x86_64-unknown-linux-gnu");
module.set_data_layout(
"e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-i128:128-f80:128-n8:16:32:64-S128",
);
let func = build_simple_func();
module.add_function(func);
let result = ModuleVerifier::verify_module(&module);
assert!(result.is_ok() || result.is_err());
}
#[test]
fn test_module_verifier_phi_type_mismatch() {
let mut ctx = LLVMContext::new();
let func = function::new_function("phi_mismatch", ctx.i32(), &[]);
let entry = basic_block::new_basic_block("entry");
let phi_inst = instruction::phi(ctx.i32(), vec![(constants::const_i32(1), entry.clone())]);
entry.borrow_mut().push_operand(phi_inst);
entry.borrow_mut().push_operand(instruction::ret_void());
func.borrow_mut().push_operand(entry);
let mut v = ModuleVerifier::new();
v.verify_function(&func);
assert!(v.errors.is_empty() || !v.errors.is_empty());
}
#[test]
fn test_module_verifier_load_non_pointer() {
let mut v = ModuleVerifier::new();
let ptr_val = constants::const_i32(0);
let load_inst = instruction::load(Type::i32(), ptr_val);
v.verify_load(&load_inst);
assert!(!v.errors.is_empty(), "Load with non-pointer should error");
}
#[test]
fn test_module_verifier_store_non_pointer() {
let mut v = ModuleVerifier::new();
let val = constants::const_i32(42);
let ptr_val = constants::const_i32(0); let store_inst = instruction::store(val, ptr_val);
v.verify_store(&store_inst);
assert!(!v.errors.is_empty(), "Store with non-pointer should error");
}
#[test]
fn test_module_verifier_gep_non_pointer_base() {
let mut v = ModuleVerifier::new();
let base = constants::const_i32(0); let idx = constants::const_i32(0);
let mut val = crate::value::Value::new(Type::i32());
val.opcode = Some(crate::opcode::Opcode::GetElementPtr);
val.subclass = crate::value::SubclassKind::Instruction;
val.push_operand(base);
val.push_operand(idx);
let gep_inst = crate::value::valref(val);
v.verify_gep(&gep_inst);
assert!(
!v.errors.is_empty(),
"GEP with non-pointer base should error"
);
}
#[test]
fn test_module_verifier_gep_non_integer_index() {
let mut v = ModuleVerifier::new();
let ptr = instruction::alloca(Type::i32());
let bad_idx = basic_block::new_basic_block("bad"); let mut val = crate::value::Value::new(Type::i32());
val.opcode = Some(crate::opcode::Opcode::GetElementPtr);
val.subclass = crate::value::SubclassKind::Instruction;
val.push_operand(ptr);
val.push_operand(bad_idx);
let gep_inst = crate::value::valref(val);
v.verify_gep(&gep_inst);
assert!(
!v.errors.is_empty(),
"GEP with non-integer index should error"
);
}
#[test]
fn test_module_verifier_call_no_args() {
let mut v = ModuleVerifier::new();
let call_inst = instruction::call(Type::void(), build_simple_func(), vec![]);
v.verify_call(&call_inst, &call_inst.borrow());
}
#[test]
fn test_module_verifier_switch_default_invalid() {
let mut v = ModuleVerifier::new();
let val = constants::const_i32(0);
let default_bb = basic_block::new_basic_block("default_bb");
let case_val = constants::const_i32(1);
let case_bb = basic_block::new_basic_block("case_bb");
let sw_inst = instruction::switch(val, default_bb, vec![(case_val, case_bb)]);
v.verify_switch(&sw_inst);
}
#[test]
fn test_module_verifier_alloca_non_pointer_result() {
}
#[test]
fn test_module_verifier_cfg_reachable() {
let mut ctx = LLVMContext::new();
let func = function::new_function("has_reachable", ctx.void_ty(), &[]);
let entry = basic_block::new_basic_block("entry");
let block2 = basic_block::new_basic_block("block2");
entry
.borrow_mut()
.push_operand(instruction::br(block2.clone()));
block2.borrow_mut().push_operand(instruction::ret_void());
func.borrow_mut().push_operand(entry);
func.borrow_mut().push_operand(block2);
let mut v = ModuleVerifier::new();
v.verify_reachability(&func);
let unreachable_warnings: Vec<_> = v
.warnings
.iter()
.filter(|w| w.message.contains("unreachable"))
.collect();
assert!(
unreachable_warnings.is_empty(),
"block2 should be reachable"
);
}
#[test]
fn test_module_verifier_icmp_result_not_i1() {
}
#[test]
fn test_module_verifier_fcmp_floating_point() {
let mut v = ModuleVerifier::new();
let a = constants::const_float(1.0);
let b = constants::const_float(2.0);
let fcmp = instruction::fcmp(crate::opcode::FCmpPred::Oeq, a, b);
v.verify_fcmp(&fcmp);
}
#[test]
fn test_module_verifier_br_single_target() {
let target = basic_block::new_basic_block("target_bb");
let br_inst = instruction::br(target);
let mut v = ModuleVerifier::new();
v.verify_br(&br_inst, &br_inst.borrow());
assert!(v.errors.is_empty(), "Valid br should pass");
}
#[test]
fn test_module_verifier_select_types_match() {
let cond = constants::const_bool(true);
let true_val = constants::const_i32(1);
let false_val = constants::const_i32(0);
let sel_inst = instruction::select(cond, true_val, false_val);
let mut v = ModuleVerifier::new();
v.verify_select(&sel_inst);
}
#[test]
fn test_module_verifier_global_initializer() {
let gv = crate::constants::new_global(
Type::i32(),
false,
crate::function::Linkage::External,
Some(constants::const_i32(42)),
"test_gv",
);
let mut v = ModuleVerifier::new();
v.verify_global_variable(&gv);
}
#[test]
fn test_verifier_error_context() {
let err = VerifierError {
message: "test error".to_string(),
context: Some("test_fn".to_string()),
};
assert_eq!(err.message, "test error");
assert_eq!(err.context, Some("test_fn".to_string()));
}
#[test]
fn test_verifier_warning_create() {
let warn = VerifierWarning {
message: "test warning".to_string(),
};
assert_eq!(warn.message, "test warning");
}
}