use itertools::Itertools;
use crate::{
context::Context,
error::IrError,
function::Function,
instruction::{FuelVmInstruction, InstOp, Predicate},
irtype::Type,
local_var::LocalVar,
metadata::{MetadataIndex, Metadatum},
printer,
value::{Value, ValueDatum},
AnalysisResult, AnalysisResultT, AnalysisResults, BinaryOpKind, Block, BlockArgument,
BranchToWithArgs, Doc, Module, Pass, PassMutability, ScopedPass, TypeOption, UnaryOpKind,
};
pub struct ModuleVerifierResult;
impl AnalysisResultT for ModuleVerifierResult {}
pub fn module_verifier(
context: &Context,
_analyses: &AnalysisResults,
module: Module,
) -> Result<AnalysisResult, IrError> {
context.verify_module(module)?;
Ok(Box::new(ModuleVerifierResult))
}
pub const MODULE_VERIFIER_NAME: &str = "module-verifier";
pub fn create_module_verifier_pass() -> Pass {
Pass {
name: MODULE_VERIFIER_NAME,
descr: "Verify module",
deps: vec![],
runner: ScopedPass::ModulePass(PassMutability::Analysis(module_verifier)),
}
}
impl<'eng> Context<'eng> {
pub fn verify(self) -> Result<Self, IrError> {
for (module, _) in &self.modules {
let module = Module(module);
self.verify_module(module)?;
}
Ok(self)
}
fn verify_module(&self, module: Module) -> Result<(), IrError> {
for function in module.function_iter(self) {
self.verify_function(module, function)?;
}
Ok(())
}
fn verify_function(&self, cur_module: Module, function: Function) -> Result<(), IrError> {
if function.get_module(self) != cur_module {
return Err(IrError::InconsistentParent(
function.get_name(self).into(),
format!("Module_Index_{:?}", cur_module.0),
format!("Module_Index_{:?}", function.get_module(self).0),
));
}
let entry_block = function.get_entry_block(self);
if entry_block.num_predecessors(self) != 0 {
return Err(IrError::VerifyEntryBlockHasPredecessors(
function.get_name(self).to_string(),
entry_block
.pred_iter(self)
.map(|block| block.get_label(self))
.collect(),
));
}
if function.num_args(self) != entry_block.num_args(self) {
return Err(IrError::VerifyBlockArgMalformed);
}
for ((_, func_arg), block_arg) in function.args_iter(self).zip(entry_block.arg_iter(self)) {
if func_arg != block_arg {
return Err(IrError::VerifyBlockArgMalformed);
}
}
for block in function.block_iter(self) {
self.verify_block(cur_module, function, block)?;
}
self.verify_metadata(function.get_metadata(self))?;
Ok(())
}
fn verify_block(
&self,
cur_module: Module,
cur_function: Function,
cur_block: Block,
) -> Result<(), IrError> {
if cur_block.get_function(self) != cur_function {
return Err(IrError::InconsistentParent(
cur_block.get_label(self),
cur_function.get_name(self).into(),
cur_block.get_function(self).get_name(self).into(),
));
}
if cur_block.num_instructions(self) <= 1 && cur_block.num_predecessors(self) == 0 {
return Ok(());
}
for (arg_idx, arg_val) in cur_block.arg_iter(self).enumerate() {
match self.values[arg_val.0].value {
ValueDatum::Argument(BlockArgument { idx, .. }) if idx == arg_idx => (),
_ => return Err(IrError::VerifyBlockArgMalformed),
}
}
let r = InstructionVerifier {
context: self,
cur_module,
cur_function,
cur_block,
}
.verify_instructions();
if let Err(error) = &r {
println!(
"Verification failed at {}::{}",
cur_function.get_name(self),
cur_block.get_label(self)
);
let block = if let Some(problematic_value) = error.get_problematic_value() {
printer::context_print(self, &|current_value: &Value, doc: Doc| {
if *current_value == *problematic_value {
doc.append(Doc::text_line(format!("\x1b[0;31m^ {}\x1b[0m", error)))
} else {
doc
}
})
} else {
printer::block_print(self, cur_function, cur_block, &|_, doc| doc)
};
println!("{}", block);
}
r?;
let (last_is_term, num_terms) =
cur_block
.instruction_iter(self)
.fold((false, 0), |(_, n), ins| {
if ins.is_terminator(self) {
(true, n + 1)
} else {
(false, n)
}
});
if !last_is_term {
Err(IrError::MissingTerminator(
cur_block.get_label(self).clone(),
))
} else if num_terms != 1 {
Err(IrError::MisplacedTerminator(
cur_block.get_label(self).clone(),
))
} else {
Ok(())
}
}
fn verify_metadata(&self, md_idx: Option<MetadataIndex>) -> Result<(), IrError> {
if let Some(md_idx) = md_idx {
match &self.metadata[md_idx.0] {
Metadatum::List(md_idcs) => {
for md_idx in md_idcs {
self.verify_metadata(Some(*md_idx))?;
}
}
Metadatum::Struct(tag, ..) => {
if tag.is_empty() {
return Err(IrError::InvalidMetadatum(
"Struct has empty tag.".to_owned(),
));
}
let mut chs = tag.chars();
let ch0 = chs.next().unwrap();
if !(ch0.is_ascii_alphabetic() || ch0 == '_')
|| chs.any(|ch| !(ch.is_ascii_alphanumeric() || ch == '_'))
{
return Err(IrError::InvalidMetadatum(format!(
"Invalid struct tag: '{tag}'."
)));
}
}
_otherwise => (),
}
}
Ok(())
}
}
struct InstructionVerifier<'a, 'eng> {
context: &'a Context<'eng>,
cur_module: Module,
cur_function: Function,
cur_block: Block,
}
impl<'a, 'eng> InstructionVerifier<'a, 'eng> {
fn verify_instructions(&self) -> Result<(), IrError> {
for ins in self.cur_block.instruction_iter(self.context) {
let value_content = &self.context.values[ins.0];
let ValueDatum::Instruction(instruction) = &value_content.value else {
unreachable!("The value must be an instruction, because it is retrieved via block instruction iterator.")
};
if instruction.parent != self.cur_block {
return Err(IrError::InconsistentParent(
format!("Instr_{:?}", ins.0),
self.cur_block.get_label(self.context),
instruction.parent.get_label(self.context),
));
}
match &instruction.op {
InstOp::AsmBlock(..) => (),
InstOp::BitCast(value, ty) => self.verify_bitcast(value, ty)?,
InstOp::UnaryOp { op, arg } => self.verify_unary_op(op, arg)?,
InstOp::BinaryOp { op, arg1, arg2 } => self.verify_binary_op(op, arg1, arg2)?,
InstOp::Branch(block) => self.verify_br(block)?,
InstOp::Call(func, args) => self.verify_call(func, args)?,
InstOp::CastPtr(val, ty) => self.verify_cast_ptr(val, ty)?,
InstOp::Cmp(pred, lhs_value, rhs_value) => {
self.verify_cmp(pred, lhs_value, rhs_value)?
}
InstOp::ConditionalBranch {
cond_value,
true_block,
false_block,
} => self.verify_cbr(cond_value, true_block, false_block)?,
InstOp::ContractCall {
params,
coins,
asset_id,
gas,
..
} => self.verify_contract_call(params, coins, asset_id, gas)?,
InstOp::FuelVm(fuel_vm_instr) => match fuel_vm_instr {
FuelVmInstruction::Gtf { index, tx_field_id } => {
self.verify_gtf(index, tx_field_id)?
}
FuelVmInstruction::Log {
log_val,
log_ty,
log_id,
} => self.verify_log(log_val, log_ty, log_id)?,
FuelVmInstruction::ReadRegister(_) => (),
FuelVmInstruction::JmpMem => (),
FuelVmInstruction::Revert(val) => self.verify_revert(val)?,
FuelVmInstruction::Smo {
recipient,
message,
message_size,
coins,
} => self.verify_smo(recipient, message, message_size, coins)?,
FuelVmInstruction::StateClear {
key,
number_of_slots,
} => self.verify_state_clear(key, number_of_slots)?,
FuelVmInstruction::StateLoadWord(key) => self.verify_state_load_word(key)?,
FuelVmInstruction::StateLoadQuadWord {
load_val: dst_val,
key,
number_of_slots,
}
| FuelVmInstruction::StateStoreQuadWord {
stored_val: dst_val,
key,
number_of_slots,
} => self.verify_state_access_quad(dst_val, key, number_of_slots)?,
FuelVmInstruction::StateStoreWord {
stored_val: dst_val,
key,
} => self.verify_state_store_word(dst_val, key)?,
FuelVmInstruction::WideUnaryOp { op, result, arg } => {
self.verify_wide_unary_op(op, result, arg)?
}
FuelVmInstruction::WideBinaryOp {
op,
result,
arg1,
arg2,
} => self.verify_wide_binary_op(op, result, arg1, arg2)?,
FuelVmInstruction::WideModularOp {
op,
result,
arg1,
arg2,
arg3,
} => self.verify_wide_modular_op(op, result, arg1, arg2, arg3)?,
FuelVmInstruction::WideCmpOp { op, arg1, arg2 } => {
self.verify_wide_cmp(op, arg1, arg2)?
}
FuelVmInstruction::Retd { .. } => (),
},
InstOp::GetElemPtr {
base,
elem_ptr_ty,
indices,
} => self.verify_get_elem_ptr(&ins, base, elem_ptr_ty, indices)?,
InstOp::GetLocal(local_var) => self.verify_get_local(local_var)?,
InstOp::GetConfig(_, name) => self.verify_get_config(self.cur_module, name)?,
InstOp::IntToPtr(value, ty) => self.verify_int_to_ptr(value, ty)?,
InstOp::Load(ptr) => self.verify_load(ptr)?,
InstOp::MemCopyBytes {
dst_val_ptr,
src_val_ptr,
byte_len,
} => self.verify_mem_copy_bytes(dst_val_ptr, src_val_ptr, byte_len)?,
InstOp::MemCopyVal {
dst_val_ptr,
src_val_ptr,
} => self.verify_mem_copy_val(dst_val_ptr, src_val_ptr)?,
InstOp::Nop => (),
InstOp::PtrToInt(val, ty) => self.verify_ptr_to_int(val, ty)?,
InstOp::Ret(val, ty) => self.verify_ret(val, ty)?,
InstOp::Store {
dst_val_ptr,
stored_val,
} => self.verify_store(&ins, dst_val_ptr, stored_val)?,
};
self.context.verify_metadata(value_content.metadata)?;
}
Ok(())
}
fn verify_bitcast(&self, value: &Value, ty: &Type) -> Result<(), IrError> {
let val_ty = value
.get_type(self.context)
.ok_or(IrError::VerifyBitcastUnknownSourceType)?;
if self.type_bit_size(&val_ty).map_or(false, |sz| sz > 64)
|| self.type_bit_size(ty).map_or(false, |sz| sz > 64)
{
Err(IrError::VerifyBitcastBetweenInvalidTypes(
val_ty.as_string(self.context),
ty.as_string(self.context),
))
} else {
Ok(())
}
}
fn verify_unary_op(&self, op: &UnaryOpKind, arg: &Value) -> Result<(), IrError> {
let arg_ty = arg
.get_type(self.context)
.ok_or(IrError::VerifyUnaryOpIncorrectArgType)?;
match op {
UnaryOpKind::Not => {
if !arg_ty.is_uint(self.context) && !arg_ty.is_b256(self.context) {
return Err(IrError::VerifyUnaryOpIncorrectArgType);
}
}
}
Ok(())
}
fn verify_wide_cmp(&self, _: &Predicate, arg1: &Value, arg2: &Value) -> Result<(), IrError> {
let arg1_ty = arg1
.get_type(self.context)
.ok_or(IrError::VerifyBinaryOpIncorrectArgType)?;
let arg2_ty = arg2
.get_type(self.context)
.ok_or(IrError::VerifyBinaryOpIncorrectArgType)?;
if arg1_ty.is_ptr(self.context) && arg2_ty.is_ptr(self.context) {
Ok(())
} else {
Err(IrError::VerifyBinaryOpIncorrectArgType)
}
}
fn verify_wide_modular_op(
&self,
_op: &BinaryOpKind,
result: &Value,
arg1: &Value,
arg2: &Value,
arg3: &Value,
) -> Result<(), IrError> {
let result_ty = result
.get_type(self.context)
.ok_or(IrError::VerifyBinaryOpIncorrectArgType)?;
let arg1_ty = arg1
.get_type(self.context)
.ok_or(IrError::VerifyBinaryOpIncorrectArgType)?;
let arg2_ty = arg2
.get_type(self.context)
.ok_or(IrError::VerifyBinaryOpIncorrectArgType)?;
let arg3_ty = arg3
.get_type(self.context)
.ok_or(IrError::VerifyBinaryOpIncorrectArgType)?;
if !arg1_ty.is_ptr(self.context)
|| !arg2_ty.is_ptr(self.context)
|| !arg3_ty.is_ptr(self.context)
|| !result_ty.is_ptr(self.context)
{
return Err(IrError::VerifyBinaryOpIncorrectArgType);
}
Ok(())
}
fn verify_wide_binary_op(
&self,
op: &BinaryOpKind,
result: &Value,
arg1: &Value,
arg2: &Value,
) -> Result<(), IrError> {
let result_ty = result
.get_type(self.context)
.ok_or(IrError::VerifyBinaryOpIncorrectArgType)?;
let arg1_ty = arg1
.get_type(self.context)
.ok_or(IrError::VerifyBinaryOpIncorrectArgType)?;
let arg2_ty = arg2
.get_type(self.context)
.ok_or(IrError::VerifyBinaryOpIncorrectArgType)?;
match op {
BinaryOpKind::Lsh | BinaryOpKind::Rsh => {
if !arg1_ty.is_ptr(self.context)
|| !arg2_ty.is_uint64(self.context)
|| !result_ty.is_ptr(self.context)
{
return Err(IrError::VerifyBinaryOpIncorrectArgType);
}
}
BinaryOpKind::Add
| BinaryOpKind::Sub
| BinaryOpKind::Mul
| BinaryOpKind::Div
| BinaryOpKind::And
| BinaryOpKind::Or
| BinaryOpKind::Xor
| BinaryOpKind::Mod => {
if !arg1_ty.is_ptr(self.context)
|| !arg2_ty.is_ptr(self.context)
|| !result_ty.is_ptr(self.context)
{
return Err(IrError::VerifyBinaryOpIncorrectArgType);
}
}
}
Ok(())
}
fn verify_wide_unary_op(
&self,
_op: &UnaryOpKind,
result: &Value,
arg: &Value,
) -> Result<(), IrError> {
let result_ty = result
.get_type(self.context)
.ok_or(IrError::VerifyBinaryOpIncorrectArgType)?;
let arg_ty = arg
.get_type(self.context)
.ok_or(IrError::VerifyBinaryOpIncorrectArgType)?;
if !arg_ty.is_ptr(self.context) || !result_ty.is_ptr(self.context) {
return Err(IrError::VerifyBinaryOpIncorrectArgType);
}
Ok(())
}
fn verify_binary_op(
&self,
op: &BinaryOpKind,
arg1: &Value,
arg2: &Value,
) -> Result<(), IrError> {
let arg1_ty = arg1
.get_type(self.context)
.ok_or(IrError::VerifyBinaryOpIncorrectArgType)?;
let arg2_ty = arg2
.get_type(self.context)
.ok_or(IrError::VerifyBinaryOpIncorrectArgType)?;
match op {
BinaryOpKind::Lsh | BinaryOpKind::Rsh => {
let is_lhs_ok = arg1_ty.is_uint(self.context) || arg1_ty.is_b256(self.context);
if !is_lhs_ok || !arg2_ty.is_uint(self.context) {
return Err(IrError::VerifyBinaryOpIncorrectArgType);
}
}
BinaryOpKind::Add
| BinaryOpKind::Sub
| BinaryOpKind::Mul
| BinaryOpKind::Div
| BinaryOpKind::Mod => {
if !arg1_ty.eq(self.context, &arg2_ty) || !arg1_ty.is_uint(self.context) {
return Err(IrError::VerifyBinaryOpIncorrectArgType);
}
}
BinaryOpKind::And | BinaryOpKind::Or | BinaryOpKind::Xor => {
if !arg1_ty.eq(self.context, &arg2_ty)
|| !(arg1_ty.is_uint(self.context) || arg1_ty.is_b256(self.context))
{
return Err(IrError::VerifyBinaryOpIncorrectArgType);
}
}
}
Ok(())
}
fn verify_br(&self, dest_block: &BranchToWithArgs) -> Result<(), IrError> {
if !self
.cur_function
.block_iter(self.context)
.contains(&dest_block.block)
{
Err(IrError::VerifyBranchToMissingBlock(
self.context.blocks[dest_block.block.0].label.clone(),
))
} else {
self.verify_dest_args(dest_block)
}
}
fn verify_call(&self, callee: &Function, args: &[Value]) -> Result<(), IrError> {
let callee_content = &self.context.functions[callee.0];
if !self.cur_module.function_iter(self.context).contains(callee) {
return Err(IrError::VerifyCallToMissingFunction(
callee_content.name.clone(),
));
}
let callee_arg_types = callee_content
.arguments
.iter()
.map(|(_, arg_val)| {
if let ValueDatum::Argument(BlockArgument { ty, .. }) =
&self.context.values[arg_val.0].value
{
Ok(*ty)
} else {
Err(IrError::VerifyArgumentValueIsNotArgument(
callee_content.name.clone(),
))
}
})
.collect::<Result<Vec<Type>, IrError>>()?;
for (opt_caller_arg_type, callee_arg_type) in args
.iter()
.map(|val| val.get_type(self.context))
.zip(callee_arg_types.iter())
{
if opt_caller_arg_type.is_none() {
return Err(IrError::VerifyUntypedValuePassedToFunction);
}
let caller_arg_type = opt_caller_arg_type.as_ref().unwrap();
if !caller_arg_type.eq(self.context, callee_arg_type) {
return Err(IrError::VerifyCallArgTypeMismatch(
callee_content.name.clone(),
caller_arg_type.as_string(self.context),
callee_arg_type.as_string(self.context),
));
}
}
Ok(())
}
fn verify_cast_ptr(&self, val: &Value, ty: &Type) -> Result<(), IrError> {
let _ = self.get_ptr_type(val, IrError::VerifyPtrCastFromNonPointer)?;
if !ty.is_ptr(self.context) {
Err(IrError::VerifyPtrCastToNonPointer(
ty.as_string(self.context),
))
} else {
Ok(())
}
}
fn verify_dest_args(&self, dest: &BranchToWithArgs) -> Result<(), IrError> {
if dest.block.num_args(self.context) != dest.args.len() {
return Err(IrError::VerifyBranchParamsMismatch);
}
for (arg_idx, dest_param) in dest.block.arg_iter(self.context).enumerate() {
match dest.args.get(arg_idx) {
Some(actual)
if dest_param
.get_type(self.context)
.unwrap()
.eq(self.context, &actual.get_type(self.context).unwrap()) => {}
_ =>
{
}
}
}
Ok(())
}
fn verify_cbr(
&self,
cond_val: &Value,
true_block: &BranchToWithArgs,
false_block: &BranchToWithArgs,
) -> Result<(), IrError> {
if !cond_val
.get_type(self.context)
.is(Type::is_bool, self.context)
{
Err(IrError::VerifyConditionExprNotABool)
} else if !self
.cur_function
.block_iter(self.context)
.contains(&true_block.block)
{
Err(IrError::VerifyBranchToMissingBlock(
self.context.blocks[true_block.block.0].label.clone(),
))
} else if !self
.cur_function
.block_iter(self.context)
.contains(&false_block.block)
{
Err(IrError::VerifyBranchToMissingBlock(
self.context.blocks[false_block.block.0].label.clone(),
))
} else {
self.verify_dest_args(true_block)
.and_then(|()| self.verify_dest_args(false_block))
}
}
fn verify_cmp(
&self,
_pred: &Predicate,
lhs_value: &Value,
rhs_value: &Value,
) -> Result<(), IrError> {
match (
lhs_value.get_type(self.context),
rhs_value.get_type(self.context),
) {
(Some(lhs_ty), Some(rhs_ty)) => {
if !lhs_ty.eq(self.context, &rhs_ty) {
Err(IrError::VerifyCmpTypeMismatch(
lhs_ty.as_string(self.context),
rhs_ty.as_string(self.context),
))
} else if lhs_ty.is_bool(self.context)
|| lhs_ty.is_uint(self.context)
|| lhs_ty.is_b256(self.context)
{
Ok(())
} else {
Err(IrError::VerifyCmpBadTypes(
lhs_ty.as_string(self.context),
rhs_ty.as_string(self.context),
))
}
}
_otherwise => Err(IrError::VerifyCmpUnknownTypes),
}
}
fn verify_contract_call(
&self,
params: &Value,
coins: &Value,
asset_id: &Value,
gas: &Value,
) -> Result<(), IrError> {
if !self.context.experimental.new_encoding {
let fields = params
.get_type(self.context)
.and_then(|ty| ty.get_pointee_type(self.context))
.map_or_else(std::vec::Vec::new, |ty| ty.get_field_types(self.context));
if fields.len() != 3
|| !fields[0].is_b256(self.context)
|| !fields[1].is_uint64(self.context)
|| !fields[2].is_uint64(self.context)
{
Err(IrError::VerifyContractCallBadTypes("params".to_owned()))
} else {
Ok(())
}
.and_then(|_| {
if coins
.get_type(self.context)
.is(Type::is_uint64, self.context)
{
Ok(())
} else {
Err(IrError::VerifyContractCallBadTypes("coins".to_owned()))
}
})
.and_then(|_| {
if asset_id
.get_type(self.context)
.and_then(|ty| ty.get_pointee_type(self.context))
.is(Type::is_b256, self.context)
{
Ok(())
} else {
Err(IrError::VerifyContractCallBadTypes("asset_id".to_owned()))
}
})
.and_then(|_| {
if gas.get_type(self.context).is(Type::is_uint64, self.context) {
Ok(())
} else {
Err(IrError::VerifyContractCallBadTypes("gas".to_owned()))
}
})
} else {
Ok(())
}
}
fn verify_get_elem_ptr(
&self,
ins: &Value,
base: &Value,
elem_ptr_ty: &Type,
indices: &[Value],
) -> Result<(), IrError> {
use crate::constant::ConstantValue;
let base_ty =
self.get_ptr_type(base, |s| IrError::VerifyGepFromNonPointer(s, Some(*ins)))?;
if !base_ty.is_aggregate(self.context) {
return Err(IrError::VerifyGepOnNonAggregate);
}
let Some(elem_inner_ty) = elem_ptr_ty.get_pointee_type(self.context) else {
return Err(IrError::VerifyGepElementTypeNonPointer);
};
if indices.is_empty() {
return Err(IrError::VerifyGepInconsistentTypes(
"Empty Indices".into(),
Some(*base),
));
}
let index_ty = indices.iter().try_fold(base_ty, |ty, idx_val| {
idx_val
.get_constant(self.context)
.and_then(|const_ref| {
if let ConstantValue::Uint(n) = const_ref.value {
Some(n)
} else {
None
}
})
.and_then(|idx| ty.get_field_type(self.context, idx))
.or_else(|| ty.get_array_elem_type(self.context))
});
if self.opt_ty_not_eq(&Some(elem_inner_ty), &index_ty) {
return Err(IrError::VerifyGepInconsistentTypes(
format!(
"Element type \"{}\" versus index type {:?}",
elem_inner_ty.as_string(self.context),
index_ty.map(|x| x.as_string(self.context))
),
Some(*ins),
));
}
Ok(())
}
fn verify_get_local(&self, local_var: &LocalVar) -> Result<(), IrError> {
if !self.context.functions[self.cur_function.0]
.local_storage
.values()
.any(|var| var == local_var)
{
Err(IrError::VerifyGetNonExistentPointer)
} else {
Ok(())
}
}
fn verify_get_config(&self, module: Module, name: &str) -> Result<(), IrError> {
if !self.context.modules[module.0].configs.contains_key(name) {
Err(IrError::VerifyGetNonExistentPointer)
} else {
Ok(())
}
}
fn verify_gtf(&self, index: &Value, _tx_field_id: &u64) -> Result<(), IrError> {
if !index.get_type(self.context).is(Type::is_uint, self.context) {
Err(IrError::VerifyInvalidGtfIndexType)
} else {
Ok(())
}
}
fn verify_int_to_ptr(&self, value: &Value, ty: &Type) -> Result<(), IrError> {
let val_ty = value
.get_type(self.context)
.ok_or(IrError::VerifyIntToPtrUnknownSourceType)?;
if !val_ty.is_uint(self.context) {
return Err(IrError::VerifyIntToPtrFromNonIntegerType(
val_ty.as_string(self.context),
));
}
if !ty.is_ptr(self.context) {
return Err(IrError::VerifyIntToPtrToNonPointer(
ty.as_string(self.context),
));
}
Ok(())
}
fn verify_load(&self, src_val: &Value) -> Result<(), IrError> {
self.get_ptr_type(src_val, IrError::VerifyLoadFromNonPointer)
.map(|_| ())
}
fn verify_log(&self, log_val: &Value, log_ty: &Type, log_id: &Value) -> Result<(), IrError> {
if !log_id
.get_type(self.context)
.is(Type::is_uint64, self.context)
{
return Err(IrError::VerifyLogId);
}
if self.opt_ty_not_eq(&log_val.get_type(self.context), &Some(*log_ty)) {
return Err(IrError::VerifyLogMismatchedTypes);
}
Ok(())
}
fn verify_mem_copy_bytes(
&self,
dst_val_ptr: &Value,
src_val_ptr: &Value,
_byte_len: &u64,
) -> Result<(), IrError> {
self.get_ptr_type(dst_val_ptr, IrError::VerifyMemcopyNonPointer)
.and_then(|_| self.get_ptr_type(src_val_ptr, IrError::VerifyMemcopyNonPointer))
.map(|_| ())
}
fn verify_mem_copy_val(&self, dst_val_ptr: &Value, src_val_ptr: &Value) -> Result<(), IrError> {
self.get_ptr_type(dst_val_ptr, IrError::VerifyMemcopyNonPointer)
.and_then(|dst_ty| {
self.get_ptr_type(src_val_ptr, IrError::VerifyMemcopyNonPointer)
.map(|src_ty| (dst_ty, src_ty))
})
.and_then(|(dst_ty, src_ty)| {
dst_ty
.eq(self.context, &src_ty)
.then_some(())
.ok_or_else(|| {
IrError::VerifyMemcopyMismatchedTypes(
dst_ty.as_string(self.context),
src_ty.as_string(self.context),
)
})
})
}
fn verify_ptr_to_int(&self, _val: &Value, ty: &Type) -> Result<(), IrError> {
if !ty.is_uint(self.context) {
Err(IrError::VerifyPtrToIntToNonInteger(
ty.as_string(self.context),
))
} else {
Ok(())
}
}
fn verify_ret(&self, val: &Value, ty: &Type) -> Result<(), IrError> {
if !self
.cur_function
.get_return_type(self.context)
.eq(self.context, ty)
|| self.opt_ty_not_eq(&val.get_type(self.context), &Some(*ty))
{
Err(IrError::VerifyReturnMismatchedTypes(
self.cur_function.get_name(self.context).to_string(),
))
} else {
Ok(())
}
}
fn verify_revert(&self, val: &Value) -> Result<(), IrError> {
if !val.get_type(self.context).is(Type::is_uint64, self.context) {
Err(IrError::VerifyRevertCodeBadType)
} else {
Ok(())
}
}
fn verify_smo(
&self,
recipient: &Value,
message: &Value,
message_size: &Value,
coins: &Value,
) -> Result<(), IrError> {
let recipient = self.get_ptr_type(recipient, IrError::VerifySmoRecipientNonPointer)?;
if !recipient.is_b256(self.context) {
return Err(IrError::VerifySmoRecipientBadType);
}
let struct_ty = self.get_ptr_type(message, IrError::VerifySmoMessageNonPointer)?;
if !struct_ty.is_struct(self.context) {
return Err(IrError::VerifySmoBadMessageType);
}
let fields = struct_ty.get_field_types(self.context);
if fields.len() != 2 {
return Err(IrError::VerifySmoBadMessageType);
}
if !message_size
.get_type(self.context)
.is(Type::is_uint64, self.context)
{
return Err(IrError::VerifySmoMessageSize);
}
if !coins
.get_type(self.context)
.is(Type::is_uint64, self.context)
{
return Err(IrError::VerifySmoCoins);
}
Ok(())
}
fn verify_state_clear(&self, key: &Value, number_of_slots: &Value) -> Result<(), IrError> {
let key_type = self.get_ptr_type(key, IrError::VerifyStateKeyNonPointer)?;
if !key_type.is_b256(self.context) {
Err(IrError::VerifyStateKeyBadType)
} else if !number_of_slots
.get_type(self.context)
.is(Type::is_uint, self.context)
{
Err(IrError::VerifyStateAccessNumOfSlots)
} else {
Ok(())
}
}
fn verify_state_access_quad(
&self,
dst_val: &Value,
key: &Value,
number_of_slots: &Value,
) -> Result<(), IrError> {
let dst_ty = self.get_ptr_type(dst_val, IrError::VerifyStateAccessQuadNonPointer)?;
if !dst_ty.is_b256(self.context) {
return Err(IrError::VerifyStateDestBadType(
dst_ty.as_string(self.context),
));
}
let key_type = self.get_ptr_type(key, IrError::VerifyStateKeyNonPointer)?;
if !key_type.is_b256(self.context) {
return Err(IrError::VerifyStateKeyBadType);
}
if !number_of_slots
.get_type(self.context)
.is(Type::is_uint, self.context)
{
return Err(IrError::VerifyStateAccessNumOfSlots);
}
Ok(())
}
fn verify_state_load_word(&self, key: &Value) -> Result<(), IrError> {
let key_type = self.get_ptr_type(key, IrError::VerifyStateKeyNonPointer)?;
if !key_type.is_b256(self.context) {
Err(IrError::VerifyStateKeyBadType)
} else {
Ok(())
}
}
fn verify_state_store_word(&self, dst_val: &Value, key: &Value) -> Result<(), IrError> {
let key_type = self.get_ptr_type(key, IrError::VerifyStateKeyNonPointer)?;
if !key_type.is_b256(self.context) {
Err(IrError::VerifyStateKeyBadType)
} else if !dst_val
.get_type(self.context)
.is(Type::is_uint, self.context)
{
Err(IrError::VerifyStateDestBadType(
Type::get_uint64(self.context).as_string(self.context),
))
} else {
Ok(())
}
}
fn verify_store(
&self,
ins: &Value,
dst_val: &Value,
stored_val: &Value,
) -> Result<(), IrError> {
let dst_ty = self.get_ptr_type(dst_val, IrError::VerifyStoreToNonPointer)?;
let stored_ty = stored_val.get_type(self.context);
if self.opt_ty_not_eq(&Some(dst_ty), &stored_ty) {
Err(IrError::VerifyStoreMismatchedTypes(Some(*ins)))
} else {
Ok(())
}
}
fn opt_ty_not_eq(&self, l_ty: &Option<Type>, r_ty: &Option<Type>) -> bool {
l_ty.is_none() || r_ty.is_none() || !l_ty.unwrap().eq(self.context, r_ty.as_ref().unwrap())
}
fn get_ptr_type<F: FnOnce(String) -> IrError>(
&self,
val: &Value,
errfn: F,
) -> Result<Type, IrError> {
val.get_type(self.context)
.ok_or_else(|| "unknown".to_owned())
.and_then(|ptr_ty| {
ptr_ty
.get_pointee_type(self.context)
.ok_or_else(|| ptr_ty.as_string(self.context))
})
.map_err(errfn)
}
fn type_bit_size(&self, ty: &Type) -> Option<usize> {
if ty.is_unit(self.context) || ty.is_bool(self.context) {
Some(1)
} else if ty.is_uint(self.context) {
Some(ty.get_uint_width(self.context).unwrap() as usize)
} else if ty.is_b256(self.context) {
Some(256)
} else {
None
}
}
}