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, LogEventData, Module, Pass, PassMutability, ScopedPass,
20    StorageKey, TypeOption, 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<(), IrError> {
50        for (module, _) in &self.modules {
51            let module = Module(module);
52            self.verify_module(module)?;
53        }
54        Ok(())
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^ {error}\x1b[0m")))
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                        log_data,
298                    } => self.verify_log(log_val, log_ty, log_id, log_data)?,
299                    FuelVmInstruction::ReadRegister(_) => (),
300                    FuelVmInstruction::JmpMem => (),
301                    FuelVmInstruction::Revert(val) => self.verify_revert(val)?,
302                    FuelVmInstruction::Smo {
303                        recipient,
304                        message,
305                        message_size,
306                        coins,
307                    } => self.verify_smo(recipient, message, message_size, coins)?,
308                    FuelVmInstruction::StateClear {
309                        key,
310                        number_of_slots,
311                    } => self.verify_state_clear(key, number_of_slots)?,
312                    FuelVmInstruction::StateLoadWord(key) => self.verify_state_load_word(key)?,
313                    FuelVmInstruction::StateLoadQuadWord {
314                        load_val: dst_val,
315                        key,
316                        number_of_slots,
317                    }
318                    | FuelVmInstruction::StateStoreQuadWord {
319                        stored_val: dst_val,
320                        key,
321                        number_of_slots,
322                    } => self.verify_state_access_quad(dst_val, key, number_of_slots)?,
323                    FuelVmInstruction::StateStoreWord {
324                        stored_val: dst_val,
325                        key,
326                    } => self.verify_state_store_word(dst_val, key)?,
327                    FuelVmInstruction::WideUnaryOp { op, result, arg } => {
328                        self.verify_wide_unary_op(op, result, arg)?
329                    }
330                    FuelVmInstruction::WideBinaryOp {
331                        op,
332                        result,
333                        arg1,
334                        arg2,
335                    } => self.verify_wide_binary_op(op, result, arg1, arg2)?,
336                    FuelVmInstruction::WideModularOp {
337                        op,
338                        result,
339                        arg1,
340                        arg2,
341                        arg3,
342                    } => self.verify_wide_modular_op(op, result, arg1, arg2, arg3)?,
343                    FuelVmInstruction::WideCmpOp { op, arg1, arg2 } => {
344                        self.verify_wide_cmp(op, arg1, arg2)?
345                    }
346                    FuelVmInstruction::Retd { .. } => (),
347                },
348                InstOp::GetElemPtr {
349                    base,
350                    elem_ptr_ty,
351                    indices,
352                } => self.verify_get_elem_ptr(&ins, base, elem_ptr_ty, indices)?,
353                InstOp::GetLocal(local_var) => self.verify_get_local(local_var)?,
354                InstOp::GetGlobal(global_var) => self.verify_get_global(global_var)?,
355                InstOp::GetConfig(_, name) => self.verify_get_config(self.cur_module, name)?,
356                InstOp::GetStorageKey(storage_key) => self.verify_get_storage_key(storage_key)?,
357                InstOp::IntToPtr(value, ty) => self.verify_int_to_ptr(value, ty)?,
358                InstOp::Load(ptr) => self.verify_load(ptr)?,
359                InstOp::Alloc { ty, count } => self.verify_alloc(ty, count)?,
360                InstOp::MemCopyBytes {
361                    dst_val_ptr,
362                    src_val_ptr,
363                    byte_len,
364                } => self.verify_mem_copy_bytes(dst_val_ptr, src_val_ptr, byte_len)?,
365                InstOp::MemCopyVal {
366                    dst_val_ptr,
367                    src_val_ptr,
368                } => self.verify_mem_copy_val(dst_val_ptr, src_val_ptr)?,
369                InstOp::MemClearVal { dst_val_ptr } => self.verify_mem_clear_val(dst_val_ptr)?,
370                InstOp::Nop => (),
371                InstOp::PtrToInt(val, ty) => self.verify_ptr_to_int(val, ty)?,
372                InstOp::Ret(val, ty) => self.verify_ret(val, ty)?,
373                InstOp::Store {
374                    dst_val_ptr,
375                    stored_val,
376                } => self.verify_store(&ins, dst_val_ptr, stored_val)?,
377            };
378
379            // Verify the instruction metadata too.
380            self.context.verify_metadata(value_content.metadata)?;
381        }
382
383        Ok(())
384    }
385
386    fn verify_bitcast(&self, value: &Value, ty: &Type) -> Result<(), IrError> {
387        // The bitsize of bools and unit is 1 which obviously won't match a typical uint.  LLVM
388        // would use `trunc` or `zext` to make types match sizes before casting.  Until we have
389        // similar we'll just make sure the sizes are <= 64 bits.
390        let val_ty = value
391            .get_type(self.context)
392            .ok_or(IrError::VerifyBitcastUnknownSourceType)?;
393        if self.type_bit_size(&val_ty).is_some_and(|sz| sz > 64)
394            || self.type_bit_size(ty).is_some_and(|sz| sz > 64)
395        {
396            Err(IrError::VerifyBitcastBetweenInvalidTypes(
397                val_ty.as_string(self.context),
398                ty.as_string(self.context),
399            ))
400        } else {
401            Ok(())
402        }
403    }
404
405    fn verify_unary_op(&self, op: &UnaryOpKind, arg: &Value) -> Result<(), IrError> {
406        let arg_ty = arg
407            .get_type(self.context)
408            .ok_or(IrError::VerifyUnaryOpIncorrectArgType)?;
409        match op {
410            UnaryOpKind::Not => {
411                if !arg_ty.is_uint(self.context) && !arg_ty.is_b256(self.context) {
412                    return Err(IrError::VerifyUnaryOpIncorrectArgType);
413                }
414            }
415        }
416
417        Ok(())
418    }
419
420    fn verify_wide_cmp(&self, _: &Predicate, arg1: &Value, arg2: &Value) -> Result<(), IrError> {
421        let arg1_ty = arg1
422            .get_type(self.context)
423            .ok_or(IrError::VerifyBinaryOpIncorrectArgType)?;
424        let arg2_ty = arg2
425            .get_type(self.context)
426            .ok_or(IrError::VerifyBinaryOpIncorrectArgType)?;
427
428        if arg1_ty.is_ptr(self.context) && arg2_ty.is_ptr(self.context) {
429            Ok(())
430        } else {
431            Err(IrError::VerifyBinaryOpIncorrectArgType)
432        }
433    }
434
435    fn verify_wide_modular_op(
436        &self,
437        _op: &BinaryOpKind,
438        result: &Value,
439        arg1: &Value,
440        arg2: &Value,
441        arg3: &Value,
442    ) -> Result<(), IrError> {
443        let result_ty = result
444            .get_type(self.context)
445            .ok_or(IrError::VerifyBinaryOpIncorrectArgType)?;
446        let arg1_ty = arg1
447            .get_type(self.context)
448            .ok_or(IrError::VerifyBinaryOpIncorrectArgType)?;
449        let arg2_ty = arg2
450            .get_type(self.context)
451            .ok_or(IrError::VerifyBinaryOpIncorrectArgType)?;
452        let arg3_ty = arg3
453            .get_type(self.context)
454            .ok_or(IrError::VerifyBinaryOpIncorrectArgType)?;
455
456        if !arg1_ty.is_ptr(self.context)
457            || !arg2_ty.is_ptr(self.context)
458            || !arg3_ty.is_ptr(self.context)
459            || !result_ty.is_ptr(self.context)
460        {
461            return Err(IrError::VerifyBinaryOpIncorrectArgType);
462        }
463
464        Ok(())
465    }
466
467    fn verify_wide_binary_op(
468        &self,
469        op: &BinaryOpKind,
470        result: &Value,
471        arg1: &Value,
472        arg2: &Value,
473    ) -> Result<(), IrError> {
474        let result_ty = result
475            .get_type(self.context)
476            .ok_or(IrError::VerifyBinaryOpIncorrectArgType)?;
477        let arg1_ty = arg1
478            .get_type(self.context)
479            .ok_or(IrError::VerifyBinaryOpIncorrectArgType)?;
480        let arg2_ty = arg2
481            .get_type(self.context)
482            .ok_or(IrError::VerifyBinaryOpIncorrectArgType)?;
483
484        match op {
485            // Shifts rhs are 64 bits
486            BinaryOpKind::Lsh | BinaryOpKind::Rsh => {
487                if !arg1_ty.is_ptr(self.context)
488                    || !arg2_ty.is_uint64(self.context)
489                    || !result_ty.is_ptr(self.context)
490                {
491                    return Err(IrError::VerifyBinaryOpIncorrectArgType);
492                }
493            }
494            BinaryOpKind::Add
495            | BinaryOpKind::Sub
496            | BinaryOpKind::Mul
497            | BinaryOpKind::Div
498            | BinaryOpKind::And
499            | BinaryOpKind::Or
500            | BinaryOpKind::Xor
501            | BinaryOpKind::Mod => {
502                if !arg1_ty.is_ptr(self.context)
503                    || !arg2_ty.is_ptr(self.context)
504                    || !result_ty.is_ptr(self.context)
505                {
506                    return Err(IrError::VerifyBinaryOpIncorrectArgType);
507                }
508            }
509        }
510
511        Ok(())
512    }
513
514    fn verify_wide_unary_op(
515        &self,
516        _op: &UnaryOpKind,
517        result: &Value,
518        arg: &Value,
519    ) -> Result<(), IrError> {
520        let result_ty = result
521            .get_type(self.context)
522            .ok_or(IrError::VerifyBinaryOpIncorrectArgType)?;
523        let arg_ty = arg
524            .get_type(self.context)
525            .ok_or(IrError::VerifyBinaryOpIncorrectArgType)?;
526
527        if !arg_ty.is_ptr(self.context) || !result_ty.is_ptr(self.context) {
528            return Err(IrError::VerifyBinaryOpIncorrectArgType);
529        }
530
531        Ok(())
532    }
533
534    fn verify_binary_op(
535        &self,
536        op: &BinaryOpKind,
537        arg1: &Value,
538        arg2: &Value,
539    ) -> Result<(), IrError> {
540        let arg1_ty = arg1
541            .get_type(self.context)
542            .ok_or(IrError::VerifyBinaryOpIncorrectArgType)?;
543        let arg2_ty = arg2
544            .get_type(self.context)
545            .ok_or(IrError::VerifyBinaryOpIncorrectArgType)?;
546
547        match op {
548            // Shifts can have the rhs with different type
549            BinaryOpKind::Lsh | BinaryOpKind::Rsh => {
550                let is_lhs_ok = arg1_ty.is_uint(self.context) || arg1_ty.is_b256(self.context);
551                if !is_lhs_ok || !arg2_ty.is_uint(self.context) {
552                    return Err(IrError::VerifyBinaryOpIncorrectArgType);
553                }
554            }
555            BinaryOpKind::Add | BinaryOpKind::Sub => {
556                if !(arg1_ty.eq(self.context, &arg2_ty) && arg1_ty.is_uint(self.context)
557                    || arg1_ty.is_ptr(self.context) && arg2_ty.is_uint64(self.context))
558                {
559                    return Err(IrError::VerifyBinaryOpIncorrectArgType);
560                }
561            }
562            BinaryOpKind::Mul | BinaryOpKind::Div | BinaryOpKind::Mod => {
563                if !arg1_ty.eq(self.context, &arg2_ty) || !arg1_ty.is_uint(self.context) {
564                    return Err(IrError::VerifyBinaryOpIncorrectArgType);
565                }
566            }
567            BinaryOpKind::And | BinaryOpKind::Or | BinaryOpKind::Xor => {
568                if !arg1_ty.eq(self.context, &arg2_ty)
569                    || !(arg1_ty.is_uint(self.context) || arg1_ty.is_b256(self.context))
570                {
571                    return Err(IrError::VerifyBinaryOpIncorrectArgType);
572                }
573            }
574        }
575
576        Ok(())
577    }
578
579    fn verify_br(&self, dest_block: &BranchToWithArgs) -> Result<(), IrError> {
580        if !self
581            .cur_function
582            .block_iter(self.context)
583            .contains(&dest_block.block)
584        {
585            Err(IrError::VerifyBranchToMissingBlock(
586                self.context.blocks[dest_block.block.0].label.clone(),
587            ))
588        } else {
589            self.verify_dest_args(dest_block)
590        }
591    }
592
593    fn verify_call(&self, callee: &Function, args: &[Value]) -> Result<(), IrError> {
594        let callee_content = &self.context.functions[callee.0];
595        if !self.cur_module.function_iter(self.context).contains(callee) {
596            return Err(IrError::VerifyCallToMissingFunction(
597                callee_content.name.clone(),
598            ));
599        }
600
601        let callee_arg_types = callee_content
602            .arguments
603            .iter()
604            .map(|(_, arg_val)| {
605                if let ValueDatum::Argument(BlockArgument { ty, .. }) =
606                    &self.context.values[arg_val.0].value
607                {
608                    Ok(*ty)
609                } else {
610                    Err(IrError::VerifyArgumentValueIsNotArgument(
611                        callee_content.name.clone(),
612                    ))
613                }
614            })
615            .collect::<Result<Vec<Type>, IrError>>()?;
616
617        for (opt_caller_arg_type, callee_arg_type) in args
618            .iter()
619            .map(|val| val.get_type(self.context))
620            .zip(callee_arg_types.iter())
621        {
622            if opt_caller_arg_type.is_none() {
623                return Err(IrError::VerifyUntypedValuePassedToFunction);
624            }
625
626            let caller_arg_type = opt_caller_arg_type.as_ref().unwrap();
627            if !caller_arg_type.eq(self.context, callee_arg_type) {
628                return Err(IrError::VerifyCallArgTypeMismatch(
629                    callee_content.name.clone(),
630                    caller_arg_type.as_string(self.context),
631                    callee_arg_type.as_string(self.context),
632                ));
633            }
634        }
635
636        Ok(())
637    }
638
639    fn verify_cast_ptr(&self, val: &Value, ty: &Type) -> Result<(), IrError> {
640        if !(val
641            .get_type(self.context)
642            .is_some_and(|ty| ty.is_ptr(self.context)))
643        {
644            let ty = val
645                .get_type(self.context)
646                .map(|ty| ty.as_string(self.context))
647                .unwrap_or("Unknown".into());
648            return Err(IrError::VerifyPtrCastFromNonPointer(ty));
649        }
650
651        if !ty.is_ptr(self.context) {
652            Err(IrError::VerifyPtrCastToNonPointer(
653                ty.as_string(self.context),
654            ))
655        } else {
656            Ok(())
657        }
658    }
659
660    fn verify_dest_args(&self, dest: &BranchToWithArgs) -> Result<(), IrError> {
661        if dest.block.num_args(self.context) != dest.args.len() {
662            return Err(IrError::VerifyBranchParamsMismatch);
663        }
664        for (arg_idx, dest_param) in dest.block.arg_iter(self.context).enumerate() {
665            match dest.args.get(arg_idx) {
666                Some(actual)
667                    if dest_param
668                        .get_type(self.context)
669                        .unwrap()
670                        .eq(self.context, &actual.get_type(self.context).unwrap()) => {}
671                _ =>
672                // TODO: https://github.com/FuelLabs/sway/pull/2880
673                {
674                    // return Err(IrError::VerifyBranchParamsMismatch)
675                }
676            }
677        }
678        Ok(())
679    }
680
681    fn verify_cbr(
682        &self,
683        cond_val: &Value,
684        true_block: &BranchToWithArgs,
685        false_block: &BranchToWithArgs,
686    ) -> Result<(), IrError> {
687        if !cond_val
688            .get_type(self.context)
689            .is(Type::is_bool, self.context)
690        {
691            Err(IrError::VerifyConditionExprNotABool)
692        } else if !self
693            .cur_function
694            .block_iter(self.context)
695            .contains(&true_block.block)
696        {
697            Err(IrError::VerifyBranchToMissingBlock(
698                self.context.blocks[true_block.block.0].label.clone(),
699            ))
700        } else if !self
701            .cur_function
702            .block_iter(self.context)
703            .contains(&false_block.block)
704        {
705            Err(IrError::VerifyBranchToMissingBlock(
706                self.context.blocks[false_block.block.0].label.clone(),
707            ))
708        } else {
709            self.verify_dest_args(true_block)
710                .and_then(|()| self.verify_dest_args(false_block))
711        }
712    }
713
714    fn verify_cmp(
715        &self,
716        _pred: &Predicate,
717        lhs_value: &Value,
718        rhs_value: &Value,
719    ) -> Result<(), IrError> {
720        // Comparisons must be between integers or equivalent pointers at this stage.
721        match (
722            lhs_value.get_type(self.context),
723            rhs_value.get_type(self.context),
724        ) {
725            (Some(lhs_ty), Some(rhs_ty)) => {
726                if !lhs_ty.eq(self.context, &rhs_ty) {
727                    Err(IrError::VerifyCmpTypeMismatch(
728                        lhs_ty.as_string(self.context),
729                        rhs_ty.as_string(self.context),
730                    ))
731                } else if lhs_ty.is_bool(self.context)
732                    || lhs_ty.is_uint(self.context)
733                    || lhs_ty.is_ptr(self.context)
734                    || lhs_ty.is_b256(self.context)
735                {
736                    Ok(())
737                } else {
738                    Err(IrError::VerifyCmpBadTypes(
739                        lhs_ty.as_string(self.context),
740                        rhs_ty.as_string(self.context),
741                    ))
742                }
743            }
744            _otherwise => Err(IrError::VerifyCmpUnknownTypes),
745        }
746    }
747
748    fn verify_contract_call(
749        &self,
750        params: &Value,
751        coins: &Value,
752        asset_id: &Value,
753        gas: &Value,
754    ) -> Result<(), IrError> {
755        if !self.context.experimental.new_encoding {
756            // - The params must be a struct with the B256 address, u64 selector and u64 address to
757            //   user args.
758            // - The coins and gas must be u64s.
759            // - The asset_id must be a B256
760            let fields = params
761                .get_type(self.context)
762                .and_then(|ty| ty.get_pointee_type(self.context))
763                .map_or_else(std::vec::Vec::new, |ty| ty.get_field_types(self.context));
764            if fields.len() != 3
765                || !fields[0].is_b256(self.context)
766                || !fields[1].is_uint64(self.context)
767                || !fields[2].is_uint64(self.context)
768            {
769                Err(IrError::VerifyContractCallBadTypes("params".to_owned()))
770            } else {
771                Ok(())
772            }
773            .and_then(|_| {
774                if coins
775                    .get_type(self.context)
776                    .is(Type::is_uint64, self.context)
777                {
778                    Ok(())
779                } else {
780                    Err(IrError::VerifyContractCallBadTypes("coins".to_owned()))
781                }
782            })
783            .and_then(|_| {
784                if asset_id
785                    .get_type(self.context)
786                    .and_then(|ty| ty.get_pointee_type(self.context))
787                    .is(Type::is_b256, self.context)
788                {
789                    Ok(())
790                } else {
791                    Err(IrError::VerifyContractCallBadTypes("asset_id".to_owned()))
792                }
793            })
794            .and_then(|_| {
795                if gas.get_type(self.context).is(Type::is_uint64, self.context) {
796                    Ok(())
797                } else {
798                    Err(IrError::VerifyContractCallBadTypes("gas".to_owned()))
799                }
800            })
801        } else {
802            Ok(())
803        }
804    }
805
806    fn verify_get_elem_ptr(
807        &self,
808        ins: &Value,
809        base: &Value,
810        elem_ptr_ty: &Type,
811        indices: &[Value],
812    ) -> Result<(), IrError> {
813        let base_ty =
814            self.get_ptr_type(base, |s| IrError::VerifyGepFromNonPointer(s, Some(*ins)))?;
815        if !base_ty.is_aggregate(self.context) {
816            return Err(IrError::VerifyGepOnNonAggregate);
817        }
818
819        let Some(elem_inner_ty) = elem_ptr_ty.get_pointee_type(self.context) else {
820            return Err(IrError::VerifyGepElementTypeNonPointer);
821        };
822
823        if indices.is_empty() {
824            return Err(IrError::VerifyGepInconsistentTypes(
825                "Empty Indices".into(),
826                Some(*base),
827            ));
828        }
829
830        let index_ty = base_ty.get_value_indexed_type(self.context, indices);
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::VerifyGetNonExistentLocalVarPointer)
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::VerifyGetNonExistentGlobalVarPointer)
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::VerifyGetNonExistentConfigPointer)
873        } else {
874            Ok(())
875        }
876    }
877
878    fn verify_get_storage_key(&self, storage_key: &StorageKey) -> Result<(), IrError> {
879        if !self.context.modules[self.cur_module.0]
880            .storage_keys
881            .values()
882            .any(|key| key == storage_key)
883        {
884            Err(IrError::VerifyGetNonExistentStorageKeyPointer)
885        } else {
886            Ok(())
887        }
888    }
889
890    fn verify_gtf(&self, index: &Value, _tx_field_id: &u64) -> Result<(), IrError> {
891        // We should perhaps verify that _tx_field_id fits in a twelve bit immediate
892        if !index.get_type(self.context).is(Type::is_uint, self.context) {
893            Err(IrError::VerifyInvalidGtfIndexType)
894        } else {
895            Ok(())
896        }
897    }
898
899    fn verify_int_to_ptr(&self, value: &Value, ty: &Type) -> Result<(), IrError> {
900        // We want the source value to be an integer and the destination type to be a pointer.
901        let val_ty = value
902            .get_type(self.context)
903            .ok_or(IrError::VerifyIntToPtrUnknownSourceType)?;
904        if !val_ty.is_uint(self.context) {
905            return Err(IrError::VerifyIntToPtrFromNonIntegerType(
906                val_ty.as_string(self.context),
907            ));
908        }
909        if !ty.is_ptr(self.context) {
910            return Err(IrError::VerifyIntToPtrToNonPointer(
911                ty.as_string(self.context),
912            ));
913        }
914
915        Ok(())
916    }
917
918    fn verify_load(&self, src_val: &Value) -> Result<(), IrError> {
919        // Just confirm `src_val` is a pointer.
920        self.get_ptr_type(src_val, IrError::VerifyLoadFromNonPointer)
921            .map(|_| ())
922    }
923
924    fn verify_alloc(&self, _ty: &Type, count: &Value) -> Result<(), IrError> {
925        if matches!(count.get_type(self.context), Some(ty) if ty.is_uint64(self.context)) {
926            Ok(())
927        } else {
928            Err(IrError::VerifyAllocCountNotUint64)
929        }
930    }
931
932    fn verify_log(
933        &self,
934        log_val: &Value,
935        log_ty: &Type,
936        log_id: &Value,
937        log_data: &Option<LogEventData>,
938    ) -> Result<(), IrError> {
939        if !log_id
940            .get_type(self.context)
941            .is(Type::is_uint64, self.context)
942        {
943            return Err(IrError::VerifyLogId);
944        }
945
946        if self.opt_ty_not_eq(&log_val.get_type(self.context), &Some(*log_ty)) {
947            return Err(IrError::VerifyLogMismatchedTypes);
948        }
949
950        if let Some(log_data) = log_data {
951            if log_data.version() != LogEventData::CURRENT_VERSION {
952                return Err(IrError::VerifyLogEventDataVersion(log_data.version()));
953            }
954
955            if !log_data.is_event() {
956                return Err(IrError::VerifyLogEventDataInvalid(
957                    "log metadata must describe an event".into(),
958                ));
959            }
960
961            if log_data.is_indexed() {
962                if log_data.num_elements() == 0 || log_data.event_type_size() == 0 {
963                    return Err(IrError::VerifyLogEventDataInvalid(
964                        "indexed event metadata requires non-zero element count and size".into(),
965                    ));
966                }
967            } else if log_data.num_elements() != 0 || log_data.event_type_size() != 0 {
968                return Err(IrError::VerifyLogEventDataInvalid(
969                    "non-indexed event metadata must not include element size or count".into(),
970                ));
971            }
972        }
973
974        Ok(())
975    }
976
977    fn verify_mem_copy_bytes(
978        &self,
979        dst_val_ptr: &Value,
980        src_val_ptr: &Value,
981        _byte_len: &u64,
982    ) -> Result<(), IrError> {
983        // Just confirm both values are pointers.
984        self.get_ptr_type(dst_val_ptr, IrError::VerifyMemcopyNonPointer)
985            .and_then(|_| self.get_ptr_type(src_val_ptr, IrError::VerifyMemcopyNonPointer))
986            .map(|_| ())
987    }
988
989    fn verify_mem_copy_val(&self, dst_val_ptr: &Value, src_val_ptr: &Value) -> Result<(), IrError> {
990        // Check both types are pointers and to the same type.
991        self.get_ptr_type(dst_val_ptr, IrError::VerifyMemcopyNonPointer)
992            .and_then(|dst_ty| {
993                self.get_ptr_type(src_val_ptr, IrError::VerifyMemcopyNonPointer)
994                    .map(|src_ty| (dst_ty, src_ty))
995            })
996            .and_then(|(dst_ty, src_ty)| {
997                dst_ty
998                    .eq(self.context, &src_ty)
999                    .then_some(())
1000                    .ok_or_else(|| {
1001                        IrError::VerifyMemcopyMismatchedTypes(
1002                            dst_ty.as_string(self.context),
1003                            src_ty.as_string(self.context),
1004                        )
1005                    })
1006            })
1007    }
1008
1009    // dst_val_ptr must be a a pointer.
1010    fn verify_mem_clear_val(&self, dst_val_ptr: &Value) -> Result<(), IrError> {
1011        let _ = self.get_ptr_type(dst_val_ptr, IrError::VerifyMemClearValNonPointer)?;
1012        Ok(())
1013    }
1014
1015    fn verify_ptr_to_int(&self, val: &Value, ty: &Type) -> Result<(), IrError> {
1016        // XXX Casting pointers to integers is a low level operation which needs to be verified in
1017        // the target specific verifier.  e.g., for Fuel it is assumed that b256s are 'reference
1018        // types' and you can to a ptr_to_int on them, but for target agnostic IR this isn't true.
1019        if !(val
1020            .get_type(self.context)
1021            .is_some_and(|ty| ty.is_ptr(self.context)))
1022        {
1023            let ty = val
1024                .get_type(self.context)
1025                .map(|ty| ty.as_string(self.context))
1026                .unwrap_or("Unknown".into());
1027            return Err(IrError::VerifyPtrCastFromNonPointer(ty));
1028        }
1029        if !ty.is_uint(self.context) {
1030            Err(IrError::VerifyPtrToIntToNonInteger(
1031                ty.as_string(self.context),
1032            ))
1033        } else {
1034            Ok(())
1035        }
1036    }
1037
1038    fn verify_ret(&self, val: &Value, ty: &Type) -> Result<(), IrError> {
1039        if !self
1040            .cur_function
1041            .get_return_type(self.context)
1042            .eq(self.context, ty)
1043            || self.opt_ty_not_eq(&val.get_type(self.context), &Some(*ty))
1044        {
1045            Err(IrError::VerifyReturnMismatchedTypes(
1046                self.cur_function.get_name(self.context).to_string(),
1047            ))
1048        } else {
1049            Ok(())
1050        }
1051    }
1052
1053    fn verify_revert(&self, val: &Value) -> Result<(), IrError> {
1054        if !val.get_type(self.context).is(Type::is_uint64, self.context) {
1055            Err(IrError::VerifyRevertCodeBadType)
1056        } else {
1057            Ok(())
1058        }
1059    }
1060
1061    fn verify_smo(
1062        &self,
1063        recipient: &Value,
1064        message: &Value,
1065        message_size: &Value,
1066        coins: &Value,
1067    ) -> Result<(), IrError> {
1068        // Check that the first operand is a `b256` representing the recipient address.
1069        let recipient = self.get_ptr_type(recipient, IrError::VerifySmoRecipientNonPointer)?;
1070        if !recipient.is_b256(self.context) {
1071            return Err(IrError::VerifySmoRecipientBadType);
1072        }
1073
1074        // Check that the second operand is a struct with two fields
1075        let struct_ty = self.get_ptr_type(message, IrError::VerifySmoMessageNonPointer)?;
1076
1077        if !struct_ty.is_struct(self.context) {
1078            return Err(IrError::VerifySmoBadMessageType);
1079        }
1080        let fields = struct_ty.get_field_types(self.context);
1081        if fields.len() != 2 {
1082            return Err(IrError::VerifySmoBadMessageType);
1083        }
1084
1085        // Check that the second operand is a `u64` representing the message size.
1086        if !message_size
1087            .get_type(self.context)
1088            .is(Type::is_uint64, self.context)
1089        {
1090            return Err(IrError::VerifySmoMessageSize);
1091        }
1092
1093        // Check that the third operand is a `u64` representing the amount of coins being sent.
1094        if !coins
1095            .get_type(self.context)
1096            .is(Type::is_uint64, self.context)
1097        {
1098            return Err(IrError::VerifySmoCoins);
1099        }
1100
1101        Ok(())
1102    }
1103
1104    fn verify_state_clear(&self, key: &Value, number_of_slots: &Value) -> Result<(), IrError> {
1105        let key_type = self.get_ptr_type(key, IrError::VerifyStateKeyNonPointer)?;
1106        if !key_type.is_b256(self.context) {
1107            Err(IrError::VerifyStateKeyBadType)
1108        } else if !number_of_slots
1109            .get_type(self.context)
1110            .is(Type::is_uint, self.context)
1111        {
1112            Err(IrError::VerifyStateAccessNumOfSlots)
1113        } else {
1114            Ok(())
1115        }
1116    }
1117
1118    fn verify_state_access_quad(
1119        &self,
1120        dst_val: &Value,
1121        key: &Value,
1122        number_of_slots: &Value,
1123    ) -> Result<(), IrError> {
1124        let dst_ty = self.get_ptr_type(dst_val, IrError::VerifyStateAccessQuadNonPointer)?;
1125        if !dst_ty.is_b256(self.context) {
1126            return Err(IrError::VerifyStateDestBadType(
1127                dst_ty.as_string(self.context),
1128            ));
1129        }
1130        let key_type = self.get_ptr_type(key, IrError::VerifyStateKeyNonPointer)?;
1131        if !key_type.is_b256(self.context) {
1132            return Err(IrError::VerifyStateKeyBadType);
1133        }
1134        if !number_of_slots
1135            .get_type(self.context)
1136            .is(Type::is_uint, self.context)
1137        {
1138            return Err(IrError::VerifyStateAccessNumOfSlots);
1139        }
1140        Ok(())
1141    }
1142
1143    fn verify_state_load_word(&self, key: &Value) -> Result<(), IrError> {
1144        let key_type = self.get_ptr_type(key, IrError::VerifyStateKeyNonPointer)?;
1145        if !key_type.is_b256(self.context) {
1146            Err(IrError::VerifyStateKeyBadType)
1147        } else {
1148            Ok(())
1149        }
1150    }
1151
1152    fn verify_state_store_word(&self, dst_val: &Value, key: &Value) -> Result<(), IrError> {
1153        let key_type = self.get_ptr_type(key, IrError::VerifyStateKeyNonPointer)?;
1154        if !key_type.is_b256(self.context) {
1155            Err(IrError::VerifyStateKeyBadType)
1156        } else if !dst_val
1157            .get_type(self.context)
1158            .is(Type::is_uint, self.context)
1159        {
1160            Err(IrError::VerifyStateDestBadType(
1161                Type::get_uint64(self.context).as_string(self.context),
1162            ))
1163        } else {
1164            Ok(())
1165        }
1166    }
1167
1168    fn verify_store(
1169        &self,
1170        ins: &Value,
1171        dst_val: &Value,
1172        stored_val: &Value,
1173    ) -> Result<(), IrError> {
1174        let dst_ty = self.get_ptr_type(dst_val, IrError::VerifyStoreToNonPointer)?;
1175        let stored_ty = stored_val.get_type(self.context);
1176        if self.opt_ty_not_eq(&Some(dst_ty), &stored_ty) {
1177            Err(IrError::VerifyStoreMismatchedTypes(Some(*ins)))
1178        } else {
1179            Ok(())
1180        }
1181    }
1182
1183    //----------------------------------------------------------------------------------------------
1184
1185    // This is a really common operation above... calling `Value::get_type()` and then failing when
1186    // two don't match.
1187    fn opt_ty_not_eq(&self, l_ty: &Option<Type>, r_ty: &Option<Type>) -> bool {
1188        l_ty.is_none() || r_ty.is_none() || !l_ty.unwrap().eq(self.context, r_ty.as_ref().unwrap())
1189    }
1190
1191    fn get_ptr_type<F: FnOnce(String) -> IrError>(
1192        &self,
1193        val: &Value,
1194        errfn: F,
1195    ) -> Result<Type, IrError> {
1196        val.get_type(self.context)
1197            .ok_or_else(|| "unknown".to_owned())
1198            .and_then(|ptr_ty| {
1199                ptr_ty
1200                    .get_pointee_type(self.context)
1201                    .ok_or_else(|| ptr_ty.as_string(self.context))
1202            })
1203            .map_err(errfn)
1204    }
1205
1206    // Get the bit size for fixed atomic types, or None for other types.
1207    fn type_bit_size(&self, ty: &Type) -> Option<usize> {
1208        // Typically we don't want to make assumptions about the size of types in the IR.  This is
1209        // here until we reintroduce pointers and don't need to care about type sizes (and whether
1210        // they'd fit in a 64 bit register).
1211        if ty.is_unit(self.context) || ty.is_bool(self.context) {
1212            Some(1)
1213        } else if ty.is_uint(self.context) {
1214            Some(ty.get_uint_width(self.context).unwrap() as usize)
1215        } else if ty.is_b256(self.context) {
1216            Some(256)
1217        } else {
1218            None
1219        }
1220    }
1221}