1use indexmap::IndexSet;
6use rustc_hash::FxHashSet;
7use sway_types::{FxIndexMap, FxIndexSet};
8
9use crate::{
10 AnalysisResult, AnalysisResultT, AnalysisResults, BlockArgument, Context, FuelVmInstruction,
11 Function, InstOp, Instruction, IrError, LocalVar, Pass, PassMutability, ScopedPass, Type,
12 Value, ValueDatum,
13};
14
15pub const ESCAPED_SYMBOLS_NAME: &str = "escaped-symbols";
16
17pub fn create_escaped_symbols_pass() -> Pass {
18 Pass {
19 name: ESCAPED_SYMBOLS_NAME,
20 descr: "Symbols that escape or cannot be analyzed",
21 deps: vec![],
22 runner: ScopedPass::FunctionPass(PassMutability::Analysis(compute_escaped_symbols_pass)),
23 }
24}
25
26#[derive(Debug, Eq, PartialEq, Copy, Clone, Hash)]
27pub enum Symbol {
28 Local(LocalVar),
29 Arg(BlockArgument),
30}
31
32impl Symbol {
33 pub fn get_type(&self, context: &Context) -> Type {
34 match self {
35 Symbol::Local(l) => l.get_type(context),
36 Symbol::Arg(ba) => ba.ty,
37 }
38 }
39
40 pub fn _get_name(&self, context: &Context, function: Function) -> String {
41 match self {
42 Symbol::Local(l) => function.lookup_local_name(context, l).unwrap().clone(),
43 Symbol::Arg(ba) => format!("{}[{}]", ba.block.get_label(context), ba.idx),
44 }
45 }
46}
47
48pub fn get_gep_referred_symbols(context: &Context, val: Value) -> FxIndexSet<Symbol> {
62 match get_symbols(context, val, true) {
63 ReferredSymbols::Complete(symbols) => symbols,
64 _ => unreachable!(
65 "In the case of GEP access, the set of returned symbols is always complete."
66 ),
67 }
68}
69
70pub enum ReferredSymbols {
76 Complete(FxIndexSet<Symbol>),
82 Incomplete(FxIndexSet<Symbol>),
86}
87
88impl ReferredSymbols {
89 pub fn new(is_complete: bool, symbols: FxIndexSet<Symbol>) -> Self {
90 if is_complete {
91 Self::Complete(symbols)
92 } else {
93 Self::Incomplete(symbols)
94 }
95 }
96
97 pub fn consume(self) -> (bool, FxIndexSet<Symbol>) {
100 let is_complete = matches!(self, ReferredSymbols::Complete(_));
101 let syms = match self {
102 ReferredSymbols::Complete(syms) | ReferredSymbols::Incomplete(syms) => syms,
103 };
104
105 (is_complete, syms)
106 }
107}
108
109pub fn get_referred_symbols(context: &Context, val: Value) -> ReferredSymbols {
136 get_symbols(context, val, false)
137}
138
139fn get_symbols(context: &Context, val: Value, gep_only: bool) -> ReferredSymbols {
149 fn get_symbols_rec(
152 context: &Context,
153 symbols: &mut FxIndexSet<Symbol>,
154 visited: &mut FxHashSet<Value>,
155 ptr: Value,
156 gep_only: bool,
157 is_complete: &mut bool,
158 ) {
159 fn get_argument_symbols(
160 context: &Context,
161 symbols: &mut FxIndexSet<Symbol>,
162 visited: &mut FxHashSet<Value>,
163 arg: BlockArgument,
164 gep_only: bool,
165 is_complete: &mut bool,
166 ) {
167 if arg.block.get_label(context) == "entry" {
168 symbols.insert(Symbol::Arg(arg));
169 } else {
170 arg.block
171 .pred_iter(context)
172 .map(|pred| arg.get_val_coming_from(context, pred).unwrap())
173 .for_each(|v| {
174 get_symbols_rec(context, symbols, visited, v, gep_only, is_complete)
175 })
176 }
177 }
178
179 fn get_symbols_from_u64_address_argument(
180 context: &Context,
181 symbols: &mut FxIndexSet<Symbol>,
182 visited: &mut FxHashSet<Value>,
183 u64_address_arg: BlockArgument,
184 is_complete: &mut bool,
185 ) {
186 if u64_address_arg.block.get_label(context) == "entry" {
187 symbols.insert(Symbol::Arg(u64_address_arg));
191 } else {
192 u64_address_arg
193 .block
194 .pred_iter(context)
195 .map(|pred| u64_address_arg.get_val_coming_from(context, pred).unwrap())
196 .for_each(|v| {
197 get_symbols_from_u64_address_rec(context, symbols, visited, v, is_complete)
198 })
199 }
200 }
201
202 fn get_symbols_from_u64_address_rec(
209 context: &Context,
210 symbols: &mut FxIndexSet<Symbol>,
211 visited: &mut FxHashSet<Value>,
212 u64_address: Value,
213 is_complete: &mut bool,
214 ) {
215 match context.values[u64_address.0].value {
216 ValueDatum::Argument(arg) => get_symbols_from_u64_address_argument(
219 context,
220 symbols,
221 visited,
222 arg,
223 is_complete,
224 ),
225 ValueDatum::Instruction(Instruction {
227 op: InstOp::Load(_loaded_from),
229 ..
230 }) => {
231 *is_complete = false;
235 }
236 ValueDatum::Instruction(Instruction {
237 op: InstOp::PtrToInt(ptr_value, _),
238 ..
239 }) => get_symbols_rec(context, symbols, visited, ptr_value, false, is_complete),
240 ValueDatum::Instruction(Instruction {
242 op: InstOp::FuelVm(FuelVmInstruction::Gtf { .. }),
244 ..
245 }) => (),
246 _ => {
250 *is_complete = false;
251 }
252 }
253 }
254
255 if visited.contains(&ptr) {
256 return;
257 }
258 visited.insert(ptr);
259 match context.values[ptr.0].value {
260 ValueDatum::Instruction(Instruction {
261 op: InstOp::GetLocal(local),
262 ..
263 }) => {
264 symbols.insert(Symbol::Local(local));
265 }
266 ValueDatum::Instruction(Instruction {
267 op: InstOp::GetElemPtr { base, .. },
268 ..
269 }) => get_symbols_rec(context, symbols, visited, base, gep_only, is_complete),
270 ValueDatum::Instruction(Instruction {
271 op: InstOp::IntToPtr(u64_address, _),
272 ..
273 }) if !gep_only => get_symbols_from_u64_address_rec(
274 context,
275 symbols,
276 visited,
277 u64_address,
278 is_complete,
279 ),
280 ValueDatum::Instruction(Instruction {
283 op: InstOp::GetConfig(_, _),
284 ..
285 }) if !gep_only => (),
286 ValueDatum::Instruction(Instruction {
289 op: InstOp::GetGlobal(_),
290 ..
291 }) if !gep_only => (),
292 ValueDatum::Instruction(Instruction {
295 op: InstOp::GetStorageKey(_),
296 ..
297 }) if !gep_only => (),
298 ValueDatum::Instruction(Instruction {
301 op: InstOp::Load(loaded_from),
302 ..
303 }) if !gep_only => get_symbols_rec(
304 context,
305 symbols,
306 visited,
307 loaded_from,
308 gep_only,
309 is_complete,
310 ),
311 ValueDatum::Instruction(Instruction {
312 op: InstOp::CastPtr(ptr_to_cast, _),
313 ..
314 }) if !gep_only => get_symbols_rec(
315 context,
316 symbols,
317 visited,
318 ptr_to_cast,
319 gep_only,
320 is_complete,
321 ),
322 ValueDatum::Argument(arg) => {
323 get_argument_symbols(context, symbols, visited, arg, gep_only, is_complete)
324 }
325 ValueDatum::Constant(_) if !gep_only => (),
328 _ if !gep_only => {
329 *is_complete = false;
335 }
336 _ => (),
338 }
339 }
340
341 if !val.get_type(context).is_some_and(|t| t.is_ptr(context)) {
342 return ReferredSymbols::new(true, IndexSet::default());
343 }
344
345 let mut visited = FxHashSet::default();
346 let mut symbols = IndexSet::default();
347 let mut is_complete = true;
348
349 get_symbols_rec(
350 context,
351 &mut symbols,
352 &mut visited,
353 val,
354 gep_only,
355 &mut is_complete,
356 );
357
358 ReferredSymbols::new(is_complete, symbols)
359}
360
361pub fn get_gep_symbol(context: &Context, val: Value) -> Option<Symbol> {
362 let syms = get_gep_referred_symbols(context, val);
363 (syms.len() == 1)
364 .then(|| syms.iter().next().cloned())
365 .flatten()
366}
367
368pub fn get_referred_symbol(context: &Context, val: Value) -> Option<Symbol> {
372 let syms = get_referred_symbols(context, val);
373 match syms {
374 ReferredSymbols::Complete(syms) => (syms.len() == 1)
375 .then(|| syms.iter().next().cloned())
376 .flatten(),
377 ReferredSymbols::Incomplete(_) => None,
379 }
380}
381
382pub enum EscapedSymbols {
383 Complete(FxHashSet<Symbol>),
385 Incomplete(FxHashSet<Symbol>),
388}
389
390impl AnalysisResultT for EscapedSymbols {}
391
392pub fn compute_escaped_symbols_pass(
393 context: &Context,
394 _analyses: &AnalysisResults,
395 function: Function,
396) -> Result<AnalysisResult, IrError> {
397 Ok(Box::new(compute_escaped_symbols(context, &function)))
398}
399
400fn compute_escaped_symbols(context: &Context, function: &Function) -> EscapedSymbols {
401 let add_from_val = |result: &mut FxHashSet<Symbol>, val: &Value, is_complete: &mut bool| {
402 let (complete, syms) = get_referred_symbols(context, *val).consume();
403
404 *is_complete &= complete;
405
406 syms.iter().for_each(|s| {
407 result.insert(*s);
408 });
409 };
410
411 let mut result = FxHashSet::default();
412 let mut is_complete = true;
413
414 for (_block, inst) in function.instruction_iter(context) {
415 match &inst.get_instruction(context).unwrap().op {
416 InstOp::AsmBlock(_, args) => {
417 for arg_init in args.iter().filter_map(|arg| arg.initializer) {
418 add_from_val(&mut result, &arg_init, &mut is_complete)
419 }
420 }
421 InstOp::UnaryOp { .. } => (),
422 InstOp::BinaryOp { .. } => (),
423 InstOp::BitCast(_, _) => (),
424 InstOp::Branch(_) => (),
425 InstOp::Call(callee, args) => args
426 .iter()
427 .enumerate()
428 .filter(|(arg_idx, _arg)| {
429 !callee.is_arg_immutable(context, *arg_idx)
431 })
432 .for_each(|(_, v)| add_from_val(&mut result, v, &mut is_complete)),
433 InstOp::CastPtr(ptr, _) => add_from_val(&mut result, ptr, &mut is_complete),
434 InstOp::Cmp(_, _, _) => (),
435 InstOp::ConditionalBranch { .. } => (),
436 InstOp::ContractCall { params, .. } => {
437 add_from_val(&mut result, params, &mut is_complete)
438 }
439 InstOp::FuelVm(_) => (),
440 InstOp::GetLocal(_) => (),
441 InstOp::GetGlobal(_) => (),
442 InstOp::GetConfig(_, _) => (),
443 InstOp::GetStorageKey(_) => (),
444 InstOp::GetElemPtr { .. } => (),
445 InstOp::IntToPtr(_, _) => (),
446 InstOp::Load(_) => (),
447 InstOp::MemCopyBytes { .. } => (),
448 InstOp::MemCopyVal { .. } => (),
449 InstOp::MemClearVal { .. } => (),
450 InstOp::Nop => (),
451 InstOp::PtrToInt(v, _) => add_from_val(&mut result, v, &mut is_complete),
452 InstOp::Ret(_, _) => (),
453 InstOp::Store { stored_val, .. } => {
454 add_from_val(&mut result, stored_val, &mut is_complete)
455 }
456 }
457 }
458
459 if is_complete {
460 EscapedSymbols::Complete(result)
461 } else {
462 EscapedSymbols::Incomplete(result)
463 }
464}
465
466pub fn get_loaded_ptr_values(context: &Context, inst: Value) -> Vec<Value> {
468 match &inst.get_instruction(context).unwrap().op {
469 InstOp::UnaryOp { .. }
470 | InstOp::BinaryOp { .. }
471 | InstOp::BitCast(_, _)
472 | InstOp::Branch(_)
473 | InstOp::ConditionalBranch { .. }
474 | InstOp::Cmp(_, _, _)
475 | InstOp::Nop
476 | InstOp::CastPtr(_, _)
477 | InstOp::GetLocal(_)
478 | InstOp::GetGlobal(_)
479 | InstOp::GetConfig(_, _)
480 | InstOp::GetStorageKey(_)
481 | InstOp::GetElemPtr { .. }
482 | InstOp::IntToPtr(_, _) => vec![],
483 InstOp::PtrToInt(src_val_ptr, _) => vec![*src_val_ptr],
484 InstOp::ContractCall {
485 params,
486 coins,
487 asset_id,
488 ..
489 } => vec![*params, *coins, *asset_id],
490 InstOp::Call(_, args) => args.clone(),
491 InstOp::AsmBlock(_, args) => args.iter().filter_map(|val| val.initializer).collect(),
492 InstOp::MemClearVal { .. } => vec![],
493 InstOp::MemCopyBytes { src_val_ptr, .. }
494 | InstOp::MemCopyVal { src_val_ptr, .. }
495 | InstOp::Ret(src_val_ptr, _)
496 | InstOp::Load(src_val_ptr)
497 | InstOp::FuelVm(FuelVmInstruction::Log {
498 log_val: src_val_ptr,
499 ..
500 })
501 | InstOp::FuelVm(FuelVmInstruction::StateLoadWord(src_val_ptr))
502 | InstOp::FuelVm(FuelVmInstruction::StateStoreWord {
503 key: src_val_ptr, ..
504 })
505 | InstOp::FuelVm(FuelVmInstruction::StateLoadQuadWord {
506 key: src_val_ptr, ..
507 })
508 | InstOp::FuelVm(FuelVmInstruction::StateClear {
509 key: src_val_ptr, ..
510 }) => vec![*src_val_ptr],
511 InstOp::FuelVm(FuelVmInstruction::StateStoreQuadWord {
512 stored_val: memopd1,
513 key: memopd2,
514 ..
515 })
516 | InstOp::FuelVm(FuelVmInstruction::Smo {
517 recipient: memopd1,
518 message: memopd2,
519 ..
520 }) => vec![*memopd1, *memopd2],
521 InstOp::Store { dst_val_ptr: _, .. } => vec![],
522 InstOp::FuelVm(FuelVmInstruction::Gtf { .. })
523 | InstOp::FuelVm(FuelVmInstruction::ReadRegister(_))
524 | InstOp::FuelVm(FuelVmInstruction::Revert(_) | FuelVmInstruction::JmpMem) => vec![],
525 InstOp::FuelVm(FuelVmInstruction::WideUnaryOp { arg, .. }) => vec![*arg],
526 InstOp::FuelVm(FuelVmInstruction::WideBinaryOp { arg1, arg2, .. })
527 | InstOp::FuelVm(FuelVmInstruction::WideCmpOp { arg1, arg2, .. }) => {
528 vec![*arg1, *arg2]
529 }
530 InstOp::FuelVm(FuelVmInstruction::WideModularOp {
531 arg1, arg2, arg3, ..
532 }) => vec![*arg1, *arg2, *arg3],
533 InstOp::FuelVm(FuelVmInstruction::Retd { ptr, .. }) => vec![*ptr],
534 }
535}
536
537pub fn get_loaded_symbols(context: &Context, inst: Value) -> ReferredSymbols {
539 let mut res = IndexSet::default();
540 let mut is_complete = true;
541 for val in get_loaded_ptr_values(context, inst) {
542 let (complete, syms) = get_referred_symbols(context, val).consume();
543
544 is_complete &= complete;
545
546 for sym in syms {
547 res.insert(sym);
548 }
549 }
550
551 ReferredSymbols::new(is_complete, res)
552}
553
554pub fn get_stored_ptr_values(context: &Context, inst: Value) -> Vec<Value> {
556 match &inst.get_instruction(context).unwrap().op {
557 InstOp::UnaryOp { .. }
558 | InstOp::BinaryOp { .. }
559 | InstOp::BitCast(_, _)
560 | InstOp::Branch(_)
561 | InstOp::ConditionalBranch { .. }
562 | InstOp::Cmp(_, _, _)
563 | InstOp::Nop
564 | InstOp::PtrToInt(_, _)
565 | InstOp::Ret(_, _)
566 | InstOp::CastPtr(_, _)
567 | InstOp::GetLocal(_)
568 | InstOp::GetGlobal(_)
569 | InstOp::GetConfig(_, _)
570 | InstOp::GetStorageKey(_)
571 | InstOp::GetElemPtr { .. }
572 | InstOp::IntToPtr(_, _) => vec![],
573 InstOp::ContractCall { params, .. } => vec![*params],
574 InstOp::Call(_, args) => args.clone(),
575 InstOp::AsmBlock(_, args) => args.iter().filter_map(|val| val.initializer).collect(),
576 InstOp::MemCopyBytes { dst_val_ptr, .. }
577 | InstOp::MemCopyVal { dst_val_ptr, .. }
578 | InstOp::MemClearVal { dst_val_ptr }
579 | InstOp::Store { dst_val_ptr, .. } => vec![*dst_val_ptr],
580 InstOp::Load(_) => vec![],
581 InstOp::FuelVm(vmop) => match vmop {
582 FuelVmInstruction::Gtf { .. }
583 | FuelVmInstruction::Log { .. }
584 | FuelVmInstruction::ReadRegister(_)
585 | FuelVmInstruction::Revert(_)
586 | FuelVmInstruction::JmpMem
587 | FuelVmInstruction::Smo { .. }
588 | FuelVmInstruction::StateClear { .. } => vec![],
589 FuelVmInstruction::StateLoadQuadWord { load_val, .. } => vec![*load_val],
590 FuelVmInstruction::StateLoadWord(_) | FuelVmInstruction::StateStoreWord { .. } => {
591 vec![]
592 }
593 FuelVmInstruction::StateStoreQuadWord { stored_val: _, .. } => vec![],
594 FuelVmInstruction::WideUnaryOp { result, .. }
595 | FuelVmInstruction::WideBinaryOp { result, .. }
596 | FuelVmInstruction::WideModularOp { result, .. } => vec![*result],
597 FuelVmInstruction::WideCmpOp { .. } => vec![],
598 _ => vec![],
599 },
600 }
601}
602
603pub fn get_stored_symbols(context: &Context, inst: Value) -> ReferredSymbols {
605 let mut res = IndexSet::default();
606 let mut is_complete = true;
607 for val in get_stored_ptr_values(context, inst) {
608 let (complete, syms) = get_referred_symbols(context, val).consume();
609
610 is_complete &= complete;
611
612 for sym in syms {
613 res.insert(sym);
614 }
615 }
616
617 ReferredSymbols::new(is_complete, res)
618}
619
620pub fn combine_indices(context: &Context, val: Value) -> Option<Vec<Value>> {
622 match &context.values[val.0].value {
623 ValueDatum::Instruction(Instruction {
624 op: InstOp::GetLocal(_),
625 ..
626 }) => Some(vec![]),
627 ValueDatum::Instruction(Instruction {
628 op:
629 InstOp::GetElemPtr {
630 base,
631 elem_ptr_ty: _,
632 indices,
633 },
634 ..
635 }) => {
636 let mut base_indices = combine_indices(context, *base)?;
637 base_indices.append(&mut indices.clone());
638 Some(base_indices)
639 }
640 ValueDatum::Argument(_) => Some(vec![]),
641 _ => None,
642 }
643}
644
645pub fn get_memory_offsets(context: &Context, val: Value) -> Option<FxIndexMap<Symbol, u64>> {
649 let syms = get_gep_referred_symbols(context, val);
650
651 let mut res: FxIndexMap<Symbol, u64> = FxIndexMap::default();
652 for sym in syms {
653 let offset = sym
654 .get_type(context)
655 .get_pointee_type(context)?
656 .get_value_indexed_offset(context, &combine_indices(context, val)?)?;
657 res.insert(sym, offset);
658 }
659 Some(res)
660}
661
662pub fn may_alias(context: &Context, val1: Value, len1: u64, val2: Value, len2: u64) -> bool {
665 let (Some(mem_offsets_1), Some(mem_offsets_2)) = (
666 get_memory_offsets(context, val1),
667 get_memory_offsets(context, val2),
668 ) else {
669 return true;
670 };
671
672 for (sym1, off1) in mem_offsets_1 {
673 if let Some(off2) = mem_offsets_2.get(&sym1) {
674 if (off1 <= *off2 && (off1 + len1 > *off2)) || (*off2 <= off1 && (*off2 + len2 > off1))
676 {
677 return true;
678 }
679 }
680 }
681 false
682}
683
684pub fn must_alias(context: &Context, val1: Value, len1: u64, val2: Value, len2: u64) -> bool {
687 let (Some(mem_offsets_1), Some(mem_offsets_2)) = (
688 get_memory_offsets(context, val1),
689 get_memory_offsets(context, val2),
690 ) else {
691 return false;
692 };
693
694 if mem_offsets_1.len() != 1 || mem_offsets_2.len() != 1 {
695 return false;
696 }
697
698 let (sym1, off1) = mem_offsets_1.iter().next().unwrap();
699 let (sym2, off2) = mem_offsets_2.iter().next().unwrap();
700
701 sym1 == sym2 && off1 == off2 && len1 == len2
703}
704
705pub fn pointee_size(context: &Context, ptr_val: Value) -> u64 {
707 ptr_val
708 .get_type(context)
709 .unwrap()
710 .get_pointee_type(context)
711 .expect("Expected arg to be a pointer")
712 .size(context)
713 .in_bytes()
714}