sway_ir/
verify.rs

1//! Code to validate the IR in a [`Context`].
2//!
3//! During creation, deserialization and optimization the IR should be verified to be in a
4//! consistent valid state, using the functions in this module.
5
6use itertools::Itertools;
7
8use crate::{
9    context::Context,
10    error::IrError,
11    function::Function,
12    instruction::{FuelVmInstruction, InstOp, Predicate},
13    irtype::Type,
14    metadata::{MetadataIndex, Metadatum},
15    printer,
16    value::{Value, ValueDatum},
17    variable::LocalVar,
18    AnalysisResult, AnalysisResultT, AnalysisResults, BinaryOpKind, Block, BlockArgument,
19    BranchToWithArgs, Doc, GlobalVar, Module, Pass, PassMutability, ScopedPass, TypeOption,
20    UnaryOpKind,
21};
22
23pub struct ModuleVerifierResult;
24impl AnalysisResultT for ModuleVerifierResult {}
25
26/// Verify module
27pub fn module_verifier(
28    context: &Context,
29    _analyses: &AnalysisResults,
30    module: Module,
31) -> Result<AnalysisResult, IrError> {
32    context.verify_module(module)?;
33    Ok(Box::new(ModuleVerifierResult))
34}
35
36pub const MODULE_VERIFIER_NAME: &str = "module-verifier";
37
38pub fn create_module_verifier_pass() -> Pass {
39    Pass {
40        name: MODULE_VERIFIER_NAME,
41        descr: "Verify module",
42        deps: vec![],
43        runner: ScopedPass::ModulePass(PassMutability::Analysis(module_verifier)),
44    }
45}
46
47impl Context<'_> {
48    /// Verify the contents of this [`Context`] is valid.
49    pub fn verify(self) -> Result<Self, IrError> {
50        for (module, _) in &self.modules {
51            let module = Module(module);
52            self.verify_module(module)?;
53        }
54        Ok(self)
55    }
56
57    fn verify_module(&self, module: Module) -> Result<(), IrError> {
58        for function in module.function_iter(self) {
59            self.verify_function(module, function)?;
60        }
61
62        // Check that globals have initializers if they are not mutable.
63        for global in &self.modules[module.0].global_variables {
64            if !global.1.is_mutable(self) && global.1.get_initializer(self).is_none() {
65                let global_name = module.lookup_global_variable_name(self, global.1);
66                return Err(IrError::VerifyGlobalMissingInitializer(
67                    global_name.unwrap_or_else(|| "<unknown>".to_owned()),
68                ));
69            }
70        }
71        Ok(())
72    }
73
74    fn verify_function(&self, cur_module: Module, function: Function) -> Result<(), IrError> {
75        if function.get_module(self) != cur_module {
76            return Err(IrError::InconsistentParent(
77                function.get_name(self).into(),
78                format!("Module_Index_{:?}", cur_module.0),
79                format!("Module_Index_{:?}", function.get_module(self).0),
80            ));
81        }
82
83        let entry_block = function.get_entry_block(self);
84
85        if entry_block.num_predecessors(self) != 0 {
86            return Err(IrError::VerifyEntryBlockHasPredecessors(
87                function.get_name(self).to_string(),
88                entry_block
89                    .pred_iter(self)
90                    .map(|block| block.get_label(self))
91                    .collect(),
92            ));
93        }
94
95        // Ensure that the entry block arguments are same as function arguments.
96        if function.num_args(self) != entry_block.num_args(self) {
97            return Err(IrError::VerifyBlockArgMalformed);
98        }
99        for ((_, func_arg), block_arg) in function.args_iter(self).zip(entry_block.arg_iter(self)) {
100            if func_arg != block_arg {
101                return Err(IrError::VerifyBlockArgMalformed);
102            }
103        }
104
105        // Check that locals have initializers if they aren't mutable.
106        // TODO: This check is disabled because we incorrect create
107        //       immutable locals without initializers at many places.
108        // for local in &self.functions[function.0].local_storage {
109        //     if !local.1.is_mutable(self) && local.1.get_initializer(self).is_none() {
110        //         return Err(IrError::VerifyLocalMissingInitializer(
111        //             local.0.to_string(),
112        //             function.get_name(self).to_string(),
113        //         ));
114        //     }
115        // }
116
117        for block in function.block_iter(self) {
118            self.verify_block(cur_module, function, block)?;
119        }
120        self.verify_metadata(function.get_metadata(self))?;
121        Ok(())
122    }
123
124    fn verify_block(
125        &self,
126        cur_module: Module,
127        cur_function: Function,
128        cur_block: Block,
129    ) -> Result<(), IrError> {
130        if cur_block.get_function(self) != cur_function {
131            return Err(IrError::InconsistentParent(
132                cur_block.get_label(self),
133                cur_function.get_name(self).into(),
134                cur_block.get_function(self).get_name(self).into(),
135            ));
136        }
137
138        if cur_block.num_instructions(self) <= 1 && cur_block.num_predecessors(self) == 0 {
139            // Empty unreferenced blocks are a harmless artefact.
140            return Ok(());
141        }
142
143        for (arg_idx, arg_val) in cur_block.arg_iter(self).enumerate() {
144            match self.values[arg_val.0].value {
145                ValueDatum::Argument(BlockArgument { idx, .. }) if idx == arg_idx => (),
146                _ => return Err(IrError::VerifyBlockArgMalformed),
147            }
148        }
149
150        let r = InstructionVerifier {
151            context: self,
152            cur_module,
153            cur_function,
154            cur_block,
155        }
156        .verify_instructions();
157
158        // Help to understand the verification failure
159        // If the error knows the problematic value, prints everything with the error highlighted,
160        // if not, print only the block to help pinpoint the issue
161        if let Err(error) = &r {
162            println!(
163                "Verification failed at {}::{}",
164                cur_function.get_name(self),
165                cur_block.get_label(self)
166            );
167
168            let block = if let Some(problematic_value) = error.get_problematic_value() {
169                printer::context_print(self, &|current_value: &Value, doc: Doc| {
170                    if *current_value == *problematic_value {
171                        doc.append(Doc::text_line(format!("\x1b[0;31m^ {}\x1b[0m", error)))
172                    } else {
173                        doc
174                    }
175                })
176            } else {
177                printer::block_print(self, cur_function, cur_block, &|_, doc| doc)
178            };
179
180            println!("{}", block);
181        }
182
183        r?;
184
185        let (last_is_term, num_terms) =
186            cur_block
187                .instruction_iter(self)
188                .fold((false, 0), |(_, n), ins| {
189                    if ins.is_terminator(self) {
190                        (true, n + 1)
191                    } else {
192                        (false, n)
193                    }
194                });
195        if !last_is_term {
196            Err(IrError::MissingTerminator(
197                cur_block.get_label(self).clone(),
198            ))
199        } else if num_terms != 1 {
200            Err(IrError::MisplacedTerminator(
201                cur_block.get_label(self).clone(),
202            ))
203        } else {
204            Ok(())
205        }
206    }
207
208    fn verify_metadata(&self, md_idx: Option<MetadataIndex>) -> Result<(), IrError> {
209        // For now we check only that struct tags are valid identifiers.
210        if let Some(md_idx) = md_idx {
211            match &self.metadata[md_idx.0] {
212                Metadatum::List(md_idcs) => {
213                    for md_idx in md_idcs {
214                        self.verify_metadata(Some(*md_idx))?;
215                    }
216                }
217                Metadatum::Struct(tag, ..) => {
218                    // We could import Regex to match it, but it's a simple identifier style pattern:
219                    // alpha start char, alphanumeric for the rest, or underscore anywhere.
220                    if tag.is_empty() {
221                        return Err(IrError::InvalidMetadatum(
222                            "Struct has empty tag.".to_owned(),
223                        ));
224                    }
225                    let mut chs = tag.chars();
226                    let ch0 = chs.next().unwrap();
227                    if !(ch0.is_ascii_alphabetic() || ch0 == '_')
228                        || chs.any(|ch| !(ch.is_ascii_alphanumeric() || ch == '_'))
229                    {
230                        return Err(IrError::InvalidMetadatum(format!(
231                            "Invalid struct tag: '{tag}'."
232                        )));
233                    }
234                }
235                _otherwise => (),
236            }
237        }
238        Ok(())
239    }
240}
241
242struct InstructionVerifier<'a, 'eng> {
243    context: &'a Context<'eng>,
244    cur_module: Module,
245    cur_function: Function,
246    cur_block: Block,
247}
248
249impl InstructionVerifier<'_, '_> {
250    fn verify_instructions(&self) -> Result<(), IrError> {
251        for ins in self.cur_block.instruction_iter(self.context) {
252            let value_content = &self.context.values[ins.0];
253            let ValueDatum::Instruction(instruction) = &value_content.value else {
254                unreachable!("The value must be an instruction, because it is retrieved via block instruction iterator.")
255            };
256
257            if instruction.parent != self.cur_block {
258                return Err(IrError::InconsistentParent(
259                    format!("Instr_{:?}", ins.0),
260                    self.cur_block.get_label(self.context),
261                    instruction.parent.get_label(self.context),
262                ));
263            }
264
265            match &instruction.op {
266                InstOp::AsmBlock(..) => (),
267                InstOp::BitCast(value, ty) => self.verify_bitcast(value, ty)?,
268                InstOp::UnaryOp { op, arg } => self.verify_unary_op(op, arg)?,
269                InstOp::BinaryOp { op, arg1, arg2 } => self.verify_binary_op(op, arg1, arg2)?,
270                InstOp::Branch(block) => self.verify_br(block)?,
271                InstOp::Call(func, args) => self.verify_call(func, args)?,
272                InstOp::CastPtr(val, ty) => self.verify_cast_ptr(val, ty)?,
273                InstOp::Cmp(pred, lhs_value, rhs_value) => {
274                    self.verify_cmp(pred, lhs_value, rhs_value)?
275                }
276                InstOp::ConditionalBranch {
277                    cond_value,
278                    true_block,
279                    false_block,
280                } => self.verify_cbr(cond_value, true_block, false_block)?,
281                InstOp::ContractCall {
282                    params,
283                    coins,
284                    asset_id,
285                    gas,
286                    ..
287                } => self.verify_contract_call(params, coins, asset_id, gas)?,
288                // XXX move the fuelvm verification into a module
289                InstOp::FuelVm(fuel_vm_instr) => match fuel_vm_instr {
290                    FuelVmInstruction::Gtf { index, tx_field_id } => {
291                        self.verify_gtf(index, tx_field_id)?
292                    }
293                    FuelVmInstruction::Log {
294                        log_val,
295                        log_ty,
296                        log_id,
297                    } => self.verify_log(log_val, log_ty, log_id)?,
298                    FuelVmInstruction::ReadRegister(_) => (),
299                    FuelVmInstruction::JmpMem => (),
300                    FuelVmInstruction::Revert(val) => self.verify_revert(val)?,
301                    FuelVmInstruction::Smo {
302                        recipient,
303                        message,
304                        message_size,
305                        coins,
306                    } => self.verify_smo(recipient, message, message_size, coins)?,
307                    FuelVmInstruction::StateClear {
308                        key,
309                        number_of_slots,
310                    } => self.verify_state_clear(key, number_of_slots)?,
311                    FuelVmInstruction::StateLoadWord(key) => self.verify_state_load_word(key)?,
312                    FuelVmInstruction::StateLoadQuadWord {
313                        load_val: dst_val,
314                        key,
315                        number_of_slots,
316                    }
317                    | FuelVmInstruction::StateStoreQuadWord {
318                        stored_val: dst_val,
319                        key,
320                        number_of_slots,
321                    } => self.verify_state_access_quad(dst_val, key, number_of_slots)?,
322                    FuelVmInstruction::StateStoreWord {
323                        stored_val: dst_val,
324                        key,
325                    } => self.verify_state_store_word(dst_val, key)?,
326                    FuelVmInstruction::WideUnaryOp { op, result, arg } => {
327                        self.verify_wide_unary_op(op, result, arg)?
328                    }
329                    FuelVmInstruction::WideBinaryOp {
330                        op,
331                        result,
332                        arg1,
333                        arg2,
334                    } => self.verify_wide_binary_op(op, result, arg1, arg2)?,
335                    FuelVmInstruction::WideModularOp {
336                        op,
337                        result,
338                        arg1,
339                        arg2,
340                        arg3,
341                    } => self.verify_wide_modular_op(op, result, arg1, arg2, arg3)?,
342                    FuelVmInstruction::WideCmpOp { op, arg1, arg2 } => {
343                        self.verify_wide_cmp(op, arg1, arg2)?
344                    }
345                    FuelVmInstruction::Retd { .. } => (),
346                },
347                InstOp::GetElemPtr {
348                    base,
349                    elem_ptr_ty,
350                    indices,
351                } => self.verify_get_elem_ptr(&ins, base, elem_ptr_ty, indices)?,
352                InstOp::GetLocal(local_var) => self.verify_get_local(local_var)?,
353                InstOp::GetGlobal(global_var) => self.verify_get_global(global_var)?,
354                InstOp::GetConfig(_, name) => self.verify_get_config(self.cur_module, name)?,
355                InstOp::IntToPtr(value, ty) => self.verify_int_to_ptr(value, ty)?,
356                InstOp::Load(ptr) => self.verify_load(ptr)?,
357                InstOp::MemCopyBytes {
358                    dst_val_ptr,
359                    src_val_ptr,
360                    byte_len,
361                } => self.verify_mem_copy_bytes(dst_val_ptr, src_val_ptr, byte_len)?,
362                InstOp::MemCopyVal {
363                    dst_val_ptr,
364                    src_val_ptr,
365                } => self.verify_mem_copy_val(dst_val_ptr, src_val_ptr)?,
366                InstOp::Nop => (),
367                InstOp::PtrToInt(val, ty) => self.verify_ptr_to_int(val, ty)?,
368                InstOp::Ret(val, ty) => self.verify_ret(val, ty)?,
369                InstOp::Store {
370                    dst_val_ptr,
371                    stored_val,
372                } => self.verify_store(&ins, dst_val_ptr, stored_val)?,
373            };
374
375            // Verify the instruction metadata too.
376            self.context.verify_metadata(value_content.metadata)?;
377        }
378
379        Ok(())
380    }
381
382    fn verify_bitcast(&self, value: &Value, ty: &Type) -> Result<(), IrError> {
383        // The bitsize of bools and unit is 1 which obviously won't match a typical uint.  LLVM
384        // would use `trunc` or `zext` to make types match sizes before casting.  Until we have
385        // similar we'll just make sure the sizes are <= 64 bits.
386        let val_ty = value
387            .get_type(self.context)
388            .ok_or(IrError::VerifyBitcastUnknownSourceType)?;
389        if self.type_bit_size(&val_ty).is_some_and(|sz| sz > 64)
390            || self.type_bit_size(ty).is_some_and(|sz| sz > 64)
391        {
392            Err(IrError::VerifyBitcastBetweenInvalidTypes(
393                val_ty.as_string(self.context),
394                ty.as_string(self.context),
395            ))
396        } else {
397            Ok(())
398        }
399    }
400
401    fn verify_unary_op(&self, op: &UnaryOpKind, arg: &Value) -> Result<(), IrError> {
402        let arg_ty = arg
403            .get_type(self.context)
404            .ok_or(IrError::VerifyUnaryOpIncorrectArgType)?;
405        match op {
406            UnaryOpKind::Not => {
407                if !arg_ty.is_uint(self.context) && !arg_ty.is_b256(self.context) {
408                    return Err(IrError::VerifyUnaryOpIncorrectArgType);
409                }
410            }
411        }
412
413        Ok(())
414    }
415
416    fn verify_wide_cmp(&self, _: &Predicate, arg1: &Value, arg2: &Value) -> Result<(), IrError> {
417        let arg1_ty = arg1
418            .get_type(self.context)
419            .ok_or(IrError::VerifyBinaryOpIncorrectArgType)?;
420        let arg2_ty = arg2
421            .get_type(self.context)
422            .ok_or(IrError::VerifyBinaryOpIncorrectArgType)?;
423
424        if arg1_ty.is_ptr(self.context) && arg2_ty.is_ptr(self.context) {
425            Ok(())
426        } else {
427            Err(IrError::VerifyBinaryOpIncorrectArgType)
428        }
429    }
430
431    fn verify_wide_modular_op(
432        &self,
433        _op: &BinaryOpKind,
434        result: &Value,
435        arg1: &Value,
436        arg2: &Value,
437        arg3: &Value,
438    ) -> Result<(), IrError> {
439        let result_ty = result
440            .get_type(self.context)
441            .ok_or(IrError::VerifyBinaryOpIncorrectArgType)?;
442        let arg1_ty = arg1
443            .get_type(self.context)
444            .ok_or(IrError::VerifyBinaryOpIncorrectArgType)?;
445        let arg2_ty = arg2
446            .get_type(self.context)
447            .ok_or(IrError::VerifyBinaryOpIncorrectArgType)?;
448        let arg3_ty = arg3
449            .get_type(self.context)
450            .ok_or(IrError::VerifyBinaryOpIncorrectArgType)?;
451
452        if !arg1_ty.is_ptr(self.context)
453            || !arg2_ty.is_ptr(self.context)
454            || !arg3_ty.is_ptr(self.context)
455            || !result_ty.is_ptr(self.context)
456        {
457            return Err(IrError::VerifyBinaryOpIncorrectArgType);
458        }
459
460        Ok(())
461    }
462
463    fn verify_wide_binary_op(
464        &self,
465        op: &BinaryOpKind,
466        result: &Value,
467        arg1: &Value,
468        arg2: &Value,
469    ) -> Result<(), IrError> {
470        let result_ty = result
471            .get_type(self.context)
472            .ok_or(IrError::VerifyBinaryOpIncorrectArgType)?;
473        let arg1_ty = arg1
474            .get_type(self.context)
475            .ok_or(IrError::VerifyBinaryOpIncorrectArgType)?;
476        let arg2_ty = arg2
477            .get_type(self.context)
478            .ok_or(IrError::VerifyBinaryOpIncorrectArgType)?;
479
480        match op {
481            // Shifts rhs are 64 bits
482            BinaryOpKind::Lsh | BinaryOpKind::Rsh => {
483                if !arg1_ty.is_ptr(self.context)
484                    || !arg2_ty.is_uint64(self.context)
485                    || !result_ty.is_ptr(self.context)
486                {
487                    return Err(IrError::VerifyBinaryOpIncorrectArgType);
488                }
489            }
490            BinaryOpKind::Add
491            | BinaryOpKind::Sub
492            | BinaryOpKind::Mul
493            | BinaryOpKind::Div
494            | BinaryOpKind::And
495            | BinaryOpKind::Or
496            | BinaryOpKind::Xor
497            | BinaryOpKind::Mod => {
498                if !arg1_ty.is_ptr(self.context)
499                    || !arg2_ty.is_ptr(self.context)
500                    || !result_ty.is_ptr(self.context)
501                {
502                    return Err(IrError::VerifyBinaryOpIncorrectArgType);
503                }
504            }
505        }
506
507        Ok(())
508    }
509
510    fn verify_wide_unary_op(
511        &self,
512        _op: &UnaryOpKind,
513        result: &Value,
514        arg: &Value,
515    ) -> Result<(), IrError> {
516        let result_ty = result
517            .get_type(self.context)
518            .ok_or(IrError::VerifyBinaryOpIncorrectArgType)?;
519        let arg_ty = arg
520            .get_type(self.context)
521            .ok_or(IrError::VerifyBinaryOpIncorrectArgType)?;
522
523        if !arg_ty.is_ptr(self.context) || !result_ty.is_ptr(self.context) {
524            return Err(IrError::VerifyBinaryOpIncorrectArgType);
525        }
526
527        Ok(())
528    }
529
530    fn verify_binary_op(
531        &self,
532        op: &BinaryOpKind,
533        arg1: &Value,
534        arg2: &Value,
535    ) -> Result<(), IrError> {
536        let arg1_ty = arg1
537            .get_type(self.context)
538            .ok_or(IrError::VerifyBinaryOpIncorrectArgType)?;
539        let arg2_ty = arg2
540            .get_type(self.context)
541            .ok_or(IrError::VerifyBinaryOpIncorrectArgType)?;
542
543        match op {
544            // Shifts can have the rhs with different type
545            BinaryOpKind::Lsh | BinaryOpKind::Rsh => {
546                let is_lhs_ok = arg1_ty.is_uint(self.context) || arg1_ty.is_b256(self.context);
547                if !is_lhs_ok || !arg2_ty.is_uint(self.context) {
548                    return Err(IrError::VerifyBinaryOpIncorrectArgType);
549                }
550            }
551            BinaryOpKind::Add
552            | BinaryOpKind::Sub
553            | BinaryOpKind::Mul
554            | BinaryOpKind::Div
555            | BinaryOpKind::Mod => {
556                if !arg1_ty.eq(self.context, &arg2_ty) || !arg1_ty.is_uint(self.context) {
557                    return Err(IrError::VerifyBinaryOpIncorrectArgType);
558                }
559            }
560            BinaryOpKind::And | BinaryOpKind::Or | BinaryOpKind::Xor => {
561                if !arg1_ty.eq(self.context, &arg2_ty)
562                    || !(arg1_ty.is_uint(self.context) || arg1_ty.is_b256(self.context))
563                {
564                    return Err(IrError::VerifyBinaryOpIncorrectArgType);
565                }
566            }
567        }
568
569        Ok(())
570    }
571
572    fn verify_br(&self, dest_block: &BranchToWithArgs) -> Result<(), IrError> {
573        if !self
574            .cur_function
575            .block_iter(self.context)
576            .contains(&dest_block.block)
577        {
578            Err(IrError::VerifyBranchToMissingBlock(
579                self.context.blocks[dest_block.block.0].label.clone(),
580            ))
581        } else {
582            self.verify_dest_args(dest_block)
583        }
584    }
585
586    fn verify_call(&self, callee: &Function, args: &[Value]) -> Result<(), IrError> {
587        let callee_content = &self.context.functions[callee.0];
588        if !self.cur_module.function_iter(self.context).contains(callee) {
589            return Err(IrError::VerifyCallToMissingFunction(
590                callee_content.name.clone(),
591            ));
592        }
593
594        let callee_arg_types = callee_content
595            .arguments
596            .iter()
597            .map(|(_, arg_val)| {
598                if let ValueDatum::Argument(BlockArgument { ty, .. }) =
599                    &self.context.values[arg_val.0].value
600                {
601                    Ok(*ty)
602                } else {
603                    Err(IrError::VerifyArgumentValueIsNotArgument(
604                        callee_content.name.clone(),
605                    ))
606                }
607            })
608            .collect::<Result<Vec<Type>, IrError>>()?;
609
610        for (opt_caller_arg_type, callee_arg_type) in args
611            .iter()
612            .map(|val| val.get_type(self.context))
613            .zip(callee_arg_types.iter())
614        {
615            if opt_caller_arg_type.is_none() {
616                return Err(IrError::VerifyUntypedValuePassedToFunction);
617            }
618
619            let caller_arg_type = opt_caller_arg_type.as_ref().unwrap();
620            if !caller_arg_type.eq(self.context, callee_arg_type) {
621                return Err(IrError::VerifyCallArgTypeMismatch(
622                    callee_content.name.clone(),
623                    caller_arg_type.as_string(self.context),
624                    callee_arg_type.as_string(self.context),
625                ));
626            }
627        }
628
629        Ok(())
630    }
631
632    fn verify_cast_ptr(&self, val: &Value, ty: &Type) -> Result<(), IrError> {
633        let _ = self.get_ptr_type(val, IrError::VerifyPtrCastFromNonPointer)?;
634        if !ty.is_ptr(self.context) {
635            Err(IrError::VerifyPtrCastToNonPointer(
636                ty.as_string(self.context),
637            ))
638        } else {
639            Ok(())
640        }
641    }
642
643    fn verify_dest_args(&self, dest: &BranchToWithArgs) -> Result<(), IrError> {
644        if dest.block.num_args(self.context) != dest.args.len() {
645            return Err(IrError::VerifyBranchParamsMismatch);
646        }
647        for (arg_idx, dest_param) in dest.block.arg_iter(self.context).enumerate() {
648            match dest.args.get(arg_idx) {
649                Some(actual)
650                    if dest_param
651                        .get_type(self.context)
652                        .unwrap()
653                        .eq(self.context, &actual.get_type(self.context).unwrap()) => {}
654                _ =>
655                // TODO: https://github.com/FuelLabs/sway/pull/2880
656                {
657                    // return Err(IrError::VerifyBranchParamsMismatch)
658                }
659            }
660        }
661        Ok(())
662    }
663
664    fn verify_cbr(
665        &self,
666        cond_val: &Value,
667        true_block: &BranchToWithArgs,
668        false_block: &BranchToWithArgs,
669    ) -> Result<(), IrError> {
670        if !cond_val
671            .get_type(self.context)
672            .is(Type::is_bool, self.context)
673        {
674            Err(IrError::VerifyConditionExprNotABool)
675        } else if !self
676            .cur_function
677            .block_iter(self.context)
678            .contains(&true_block.block)
679        {
680            Err(IrError::VerifyBranchToMissingBlock(
681                self.context.blocks[true_block.block.0].label.clone(),
682            ))
683        } else if !self
684            .cur_function
685            .block_iter(self.context)
686            .contains(&false_block.block)
687        {
688            Err(IrError::VerifyBranchToMissingBlock(
689                self.context.blocks[false_block.block.0].label.clone(),
690            ))
691        } else {
692            self.verify_dest_args(true_block)
693                .and_then(|()| self.verify_dest_args(false_block))
694        }
695    }
696
697    fn verify_cmp(
698        &self,
699        _pred: &Predicate,
700        lhs_value: &Value,
701        rhs_value: &Value,
702    ) -> Result<(), IrError> {
703        // Comparisons must be between integers at this stage.
704        match (
705            lhs_value.get_type(self.context),
706            rhs_value.get_type(self.context),
707        ) {
708            (Some(lhs_ty), Some(rhs_ty)) => {
709                if !lhs_ty.eq(self.context, &rhs_ty) {
710                    Err(IrError::VerifyCmpTypeMismatch(
711                        lhs_ty.as_string(self.context),
712                        rhs_ty.as_string(self.context),
713                    ))
714                } else if lhs_ty.is_bool(self.context)
715                    || lhs_ty.is_uint(self.context)
716                    || lhs_ty.is_b256(self.context)
717                {
718                    Ok(())
719                } else {
720                    Err(IrError::VerifyCmpBadTypes(
721                        lhs_ty.as_string(self.context),
722                        rhs_ty.as_string(self.context),
723                    ))
724                }
725            }
726            _otherwise => Err(IrError::VerifyCmpUnknownTypes),
727        }
728    }
729
730    fn verify_contract_call(
731        &self,
732        params: &Value,
733        coins: &Value,
734        asset_id: &Value,
735        gas: &Value,
736    ) -> Result<(), IrError> {
737        if !self.context.experimental.new_encoding {
738            // - The params must be a struct with the B256 address, u64 selector and u64 address to
739            //   user args.
740            // - The coins and gas must be u64s.
741            // - The asset_id must be a B256
742            let fields = params
743                .get_type(self.context)
744                .and_then(|ty| ty.get_pointee_type(self.context))
745                .map_or_else(std::vec::Vec::new, |ty| ty.get_field_types(self.context));
746            if fields.len() != 3
747                || !fields[0].is_b256(self.context)
748                || !fields[1].is_uint64(self.context)
749                || !fields[2].is_uint64(self.context)
750            {
751                Err(IrError::VerifyContractCallBadTypes("params".to_owned()))
752            } else {
753                Ok(())
754            }
755            .and_then(|_| {
756                if coins
757                    .get_type(self.context)
758                    .is(Type::is_uint64, self.context)
759                {
760                    Ok(())
761                } else {
762                    Err(IrError::VerifyContractCallBadTypes("coins".to_owned()))
763                }
764            })
765            .and_then(|_| {
766                if asset_id
767                    .get_type(self.context)
768                    .and_then(|ty| ty.get_pointee_type(self.context))
769                    .is(Type::is_b256, self.context)
770                {
771                    Ok(())
772                } else {
773                    Err(IrError::VerifyContractCallBadTypes("asset_id".to_owned()))
774                }
775            })
776            .and_then(|_| {
777                if gas.get_type(self.context).is(Type::is_uint64, self.context) {
778                    Ok(())
779                } else {
780                    Err(IrError::VerifyContractCallBadTypes("gas".to_owned()))
781                }
782            })
783        } else {
784            Ok(())
785        }
786    }
787
788    fn verify_get_elem_ptr(
789        &self,
790        ins: &Value,
791        base: &Value,
792        elem_ptr_ty: &Type,
793        indices: &[Value],
794    ) -> Result<(), IrError> {
795        use crate::constant::ConstantValue;
796
797        let base_ty =
798            self.get_ptr_type(base, |s| IrError::VerifyGepFromNonPointer(s, Some(*ins)))?;
799        if !base_ty.is_aggregate(self.context) {
800            return Err(IrError::VerifyGepOnNonAggregate);
801        }
802
803        let Some(elem_inner_ty) = elem_ptr_ty.get_pointee_type(self.context) else {
804            return Err(IrError::VerifyGepElementTypeNonPointer);
805        };
806
807        if indices.is_empty() {
808            return Err(IrError::VerifyGepInconsistentTypes(
809                "Empty Indices".into(),
810                Some(*base),
811            ));
812        }
813
814        // Fetch the field type from the vector of Values.  If the value is a constant int then
815        // unwrap it and try to fetch the field type (which will fail for arrays) otherwise (i.e.,
816        // not a constant int or not a struct) fetch the array element type, which will fail for
817        // non-arrays.
818        let index_ty = indices.iter().try_fold(base_ty, |ty, idx_val| {
819            idx_val
820                .get_constant(self.context)
821                .and_then(|const_ref| {
822                    if let ConstantValue::Uint(n) = const_ref.get_content(self.context).value {
823                        Some(n)
824                    } else {
825                        None
826                    }
827                })
828                .and_then(|idx| ty.get_field_type(self.context, idx))
829                .or_else(|| ty.get_array_elem_type(self.context))
830        });
831
832        if self.opt_ty_not_eq(&Some(elem_inner_ty), &index_ty) {
833            return Err(IrError::VerifyGepInconsistentTypes(
834                format!(
835                    "Element type \"{}\" versus index type {:?}",
836                    elem_inner_ty.as_string(self.context),
837                    index_ty.map(|x| x.as_string(self.context))
838                ),
839                Some(*ins),
840            ));
841        }
842
843        Ok(())
844    }
845
846    fn verify_get_local(&self, local_var: &LocalVar) -> Result<(), IrError> {
847        if !self.context.functions[self.cur_function.0]
848            .local_storage
849            .values()
850            .any(|var| var == local_var)
851        {
852            Err(IrError::VerifyGetNonExistentPointer)
853        } else {
854            Ok(())
855        }
856    }
857
858    fn verify_get_global(&self, global_var: &GlobalVar) -> Result<(), IrError> {
859        if !self.context.modules[self.cur_module.0]
860            .global_variables
861            .values()
862            .any(|var| var == global_var)
863        {
864            Err(IrError::VerifyGetNonExistentPointer)
865        } else {
866            Ok(())
867        }
868    }
869
870    fn verify_get_config(&self, module: Module, name: &str) -> Result<(), IrError> {
871        if !self.context.modules[module.0].configs.contains_key(name) {
872            Err(IrError::VerifyGetNonExistentPointer)
873        } else {
874            Ok(())
875        }
876    }
877
878    fn verify_gtf(&self, index: &Value, _tx_field_id: &u64) -> Result<(), IrError> {
879        // We should perhaps verify that _tx_field_id fits in a twelve bit immediate
880        if !index.get_type(self.context).is(Type::is_uint, self.context) {
881            Err(IrError::VerifyInvalidGtfIndexType)
882        } else {
883            Ok(())
884        }
885    }
886
887    fn verify_int_to_ptr(&self, value: &Value, ty: &Type) -> Result<(), IrError> {
888        // We want the source value to be an integer and the destination type to be a pointer.
889        let val_ty = value
890            .get_type(self.context)
891            .ok_or(IrError::VerifyIntToPtrUnknownSourceType)?;
892        if !val_ty.is_uint(self.context) {
893            return Err(IrError::VerifyIntToPtrFromNonIntegerType(
894                val_ty.as_string(self.context),
895            ));
896        }
897        if !ty.is_ptr(self.context) {
898            return Err(IrError::VerifyIntToPtrToNonPointer(
899                ty.as_string(self.context),
900            ));
901        }
902
903        Ok(())
904    }
905
906    fn verify_load(&self, src_val: &Value) -> Result<(), IrError> {
907        // Just confirm `src_val` is a pointer.
908        self.get_ptr_type(src_val, IrError::VerifyLoadFromNonPointer)
909            .map(|_| ())
910    }
911
912    fn verify_log(&self, log_val: &Value, log_ty: &Type, log_id: &Value) -> Result<(), IrError> {
913        if !log_id
914            .get_type(self.context)
915            .is(Type::is_uint64, self.context)
916        {
917            return Err(IrError::VerifyLogId);
918        }
919
920        if self.opt_ty_not_eq(&log_val.get_type(self.context), &Some(*log_ty)) {
921            return Err(IrError::VerifyLogMismatchedTypes);
922        }
923
924        Ok(())
925    }
926
927    fn verify_mem_copy_bytes(
928        &self,
929        dst_val_ptr: &Value,
930        src_val_ptr: &Value,
931        _byte_len: &u64,
932    ) -> Result<(), IrError> {
933        // Just confirm both values are pointers.
934        self.get_ptr_type(dst_val_ptr, IrError::VerifyMemcopyNonPointer)
935            .and_then(|_| self.get_ptr_type(src_val_ptr, IrError::VerifyMemcopyNonPointer))
936            .map(|_| ())
937    }
938
939    fn verify_mem_copy_val(&self, dst_val_ptr: &Value, src_val_ptr: &Value) -> Result<(), IrError> {
940        // Check both types are pointers and to the same type.
941        self.get_ptr_type(dst_val_ptr, IrError::VerifyMemcopyNonPointer)
942            .and_then(|dst_ty| {
943                self.get_ptr_type(src_val_ptr, IrError::VerifyMemcopyNonPointer)
944                    .map(|src_ty| (dst_ty, src_ty))
945            })
946            .and_then(|(dst_ty, src_ty)| {
947                dst_ty
948                    .eq(self.context, &src_ty)
949                    .then_some(())
950                    .ok_or_else(|| {
951                        IrError::VerifyMemcopyMismatchedTypes(
952                            dst_ty.as_string(self.context),
953                            src_ty.as_string(self.context),
954                        )
955                    })
956            })
957    }
958
959    fn verify_ptr_to_int(&self, _val: &Value, ty: &Type) -> Result<(), IrError> {
960        // XXX Casting pointers to integers is a low level operation which needs to be verified in
961        // the target specific verifier.  e.g., for Fuel it is assumed that b256s are 'reference
962        // types' and you can to a ptr_to_int on them, but for target agnostic IR this isn't true.
963        //
964        // let _ = self.get_ptr_type(val, IrError::VerifyPtrCastFromNonPointer)?;
965        if !ty.is_uint(self.context) {
966            Err(IrError::VerifyPtrToIntToNonInteger(
967                ty.as_string(self.context),
968            ))
969        } else {
970            Ok(())
971        }
972    }
973
974    fn verify_ret(&self, val: &Value, ty: &Type) -> Result<(), IrError> {
975        if !self
976            .cur_function
977            .get_return_type(self.context)
978            .eq(self.context, ty)
979            || self.opt_ty_not_eq(&val.get_type(self.context), &Some(*ty))
980        {
981            Err(IrError::VerifyReturnMismatchedTypes(
982                self.cur_function.get_name(self.context).to_string(),
983            ))
984        } else {
985            Ok(())
986        }
987    }
988
989    fn verify_revert(&self, val: &Value) -> Result<(), IrError> {
990        if !val.get_type(self.context).is(Type::is_uint64, self.context) {
991            Err(IrError::VerifyRevertCodeBadType)
992        } else {
993            Ok(())
994        }
995    }
996
997    fn verify_smo(
998        &self,
999        recipient: &Value,
1000        message: &Value,
1001        message_size: &Value,
1002        coins: &Value,
1003    ) -> Result<(), IrError> {
1004        // Check that the first operand is a `b256` representing the recipient address.
1005        let recipient = self.get_ptr_type(recipient, IrError::VerifySmoRecipientNonPointer)?;
1006        if !recipient.is_b256(self.context) {
1007            return Err(IrError::VerifySmoRecipientBadType);
1008        }
1009
1010        // Check that the second operand is a struct with two fields
1011        let struct_ty = self.get_ptr_type(message, IrError::VerifySmoMessageNonPointer)?;
1012
1013        if !struct_ty.is_struct(self.context) {
1014            return Err(IrError::VerifySmoBadMessageType);
1015        }
1016        let fields = struct_ty.get_field_types(self.context);
1017        if fields.len() != 2 {
1018            return Err(IrError::VerifySmoBadMessageType);
1019        }
1020
1021        // Check that the second operand is a `u64` representing the message size.
1022        if !message_size
1023            .get_type(self.context)
1024            .is(Type::is_uint64, self.context)
1025        {
1026            return Err(IrError::VerifySmoMessageSize);
1027        }
1028
1029        // Check that the third operand is a `u64` representing the amount of coins being sent.
1030        if !coins
1031            .get_type(self.context)
1032            .is(Type::is_uint64, self.context)
1033        {
1034            return Err(IrError::VerifySmoCoins);
1035        }
1036
1037        Ok(())
1038    }
1039
1040    fn verify_state_clear(&self, key: &Value, number_of_slots: &Value) -> Result<(), IrError> {
1041        let key_type = self.get_ptr_type(key, IrError::VerifyStateKeyNonPointer)?;
1042        if !key_type.is_b256(self.context) {
1043            Err(IrError::VerifyStateKeyBadType)
1044        } else if !number_of_slots
1045            .get_type(self.context)
1046            .is(Type::is_uint, self.context)
1047        {
1048            Err(IrError::VerifyStateAccessNumOfSlots)
1049        } else {
1050            Ok(())
1051        }
1052    }
1053
1054    fn verify_state_access_quad(
1055        &self,
1056        dst_val: &Value,
1057        key: &Value,
1058        number_of_slots: &Value,
1059    ) -> Result<(), IrError> {
1060        let dst_ty = self.get_ptr_type(dst_val, IrError::VerifyStateAccessQuadNonPointer)?;
1061        if !dst_ty.is_b256(self.context) {
1062            return Err(IrError::VerifyStateDestBadType(
1063                dst_ty.as_string(self.context),
1064            ));
1065        }
1066        let key_type = self.get_ptr_type(key, IrError::VerifyStateKeyNonPointer)?;
1067        if !key_type.is_b256(self.context) {
1068            return Err(IrError::VerifyStateKeyBadType);
1069        }
1070        if !number_of_slots
1071            .get_type(self.context)
1072            .is(Type::is_uint, self.context)
1073        {
1074            return Err(IrError::VerifyStateAccessNumOfSlots);
1075        }
1076        Ok(())
1077    }
1078
1079    fn verify_state_load_word(&self, key: &Value) -> Result<(), IrError> {
1080        let key_type = self.get_ptr_type(key, IrError::VerifyStateKeyNonPointer)?;
1081        if !key_type.is_b256(self.context) {
1082            Err(IrError::VerifyStateKeyBadType)
1083        } else {
1084            Ok(())
1085        }
1086    }
1087
1088    fn verify_state_store_word(&self, dst_val: &Value, key: &Value) -> Result<(), IrError> {
1089        let key_type = self.get_ptr_type(key, IrError::VerifyStateKeyNonPointer)?;
1090        if !key_type.is_b256(self.context) {
1091            Err(IrError::VerifyStateKeyBadType)
1092        } else if !dst_val
1093            .get_type(self.context)
1094            .is(Type::is_uint, self.context)
1095        {
1096            Err(IrError::VerifyStateDestBadType(
1097                Type::get_uint64(self.context).as_string(self.context),
1098            ))
1099        } else {
1100            Ok(())
1101        }
1102    }
1103
1104    fn verify_store(
1105        &self,
1106        ins: &Value,
1107        dst_val: &Value,
1108        stored_val: &Value,
1109    ) -> Result<(), IrError> {
1110        let dst_ty = self.get_ptr_type(dst_val, IrError::VerifyStoreToNonPointer)?;
1111        let stored_ty = stored_val.get_type(self.context);
1112        if self.opt_ty_not_eq(&Some(dst_ty), &stored_ty) {
1113            Err(IrError::VerifyStoreMismatchedTypes(Some(*ins)))
1114        } else {
1115            Ok(())
1116        }
1117    }
1118
1119    //----------------------------------------------------------------------------------------------
1120
1121    // This is a really common operation above... calling `Value::get_type()` and then failing when
1122    // two don't match.
1123    fn opt_ty_not_eq(&self, l_ty: &Option<Type>, r_ty: &Option<Type>) -> bool {
1124        l_ty.is_none() || r_ty.is_none() || !l_ty.unwrap().eq(self.context, r_ty.as_ref().unwrap())
1125    }
1126
1127    fn get_ptr_type<F: FnOnce(String) -> IrError>(
1128        &self,
1129        val: &Value,
1130        errfn: F,
1131    ) -> Result<Type, IrError> {
1132        val.get_type(self.context)
1133            .ok_or_else(|| "unknown".to_owned())
1134            .and_then(|ptr_ty| {
1135                ptr_ty
1136                    .get_pointee_type(self.context)
1137                    .ok_or_else(|| ptr_ty.as_string(self.context))
1138            })
1139            .map_err(errfn)
1140    }
1141
1142    // Get the bit size for fixed atomic types, or None for other types.
1143    fn type_bit_size(&self, ty: &Type) -> Option<usize> {
1144        // Typically we don't want to make assumptions about the size of types in the IR.  This is
1145        // here until we reintroduce pointers and don't need to care about type sizes (and whether
1146        // they'd fit in a 64 bit register).
1147        if ty.is_unit(self.context) || ty.is_bool(self.context) {
1148            Some(1)
1149        } else if ty.is_uint(self.context) {
1150            Some(ty.get_uint_width(self.context).unwrap() as usize)
1151        } else if ty.is_b256(self.context) {
1152            Some(256)
1153        } else {
1154            None
1155        }
1156    }
1157}