1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
use crate::{
error::*,
metadata::{MetadataManager, StorageOperation},
parse_tree::{promote_purity, Purity},
};
use sway_ir::{Context, Function, Instruction, ValueDatum};
use sway_types::span::Span;
use std::collections::HashMap;
#[derive(Default)]
pub(crate) struct PurityChecker {
memos: HashMap<Function, (bool, bool)>,
warnings: Vec<CompileWarning>,
errors: Vec<CompileError>,
}
impl PurityChecker {
pub(crate) fn check_function(
&mut self,
context: &Context,
md_mgr: &mut MetadataManager,
function: &Function,
) -> (bool, bool) {
let (reads, writes) = function.instruction_iter(context).fold(
(false, false),
|(reads, writes), (_block, ins_value)| match &context.values[ins_value.0].value {
ValueDatum::Instruction(Instruction::StateLoadQuadWord { .. })
| ValueDatum::Instruction(Instruction::StateLoadWord(_)) => (true, writes),
ValueDatum::Instruction(Instruction::StateStoreQuadWord { .. })
| ValueDatum::Instruction(Instruction::StateStoreWord { .. }) => (reads, true),
ValueDatum::Instruction(Instruction::AsmBlock(asm_block, _args)) => {
context.asm_blocks[asm_block.0].body.iter().fold(
(reads, writes),
|(reads, writes), asm_op| match asm_op.name.as_str() {
"srw" | "srwq" => (true, writes),
"sww" | "swwq" => (reads, true),
_ => (reads, writes),
},
)
}
ValueDatum::Instruction(Instruction::Call(callee, _args)) => {
let (called_fn_reads, called_fn_writes) =
self.memos.get(callee).copied().unwrap_or_else(|| {
let r_w = self.check_function(context, md_mgr, callee);
self.memos.insert(*callee, r_w);
r_w
});
(reads || called_fn_reads, writes || called_fn_writes)
}
_otherwise => (reads, writes),
},
);
let function = &context.functions[function.0];
let attributed_purity = md_mgr.md_to_storage_op(context, function.metadata);
let span = md_mgr
.md_to_span(context, function.metadata)
.unwrap_or_else(Span::dummy);
macro_rules! mk_err {
($op_str:literal, $existing_attrib:ident, $needed_attrib:ident) => {{
self.errors.push(CompileError::ImpureInPureContext {
storage_op: $op_str,
attrs: promote_purity(Purity::$existing_attrib, Purity::$needed_attrib)
.to_attribute_syntax(),
span,
});
}};
}
macro_rules! mk_warn {
($unneeded_attrib:ident) => {{
self.warnings.push(CompileWarning {
warning_content: Warning::DeadStorageDeclarationForFunction {
unneeded_attrib: Purity::$unneeded_attrib.to_attribute_syntax(),
},
span,
});
}};
}
match (attributed_purity, reads, writes) {
(None, true, false) => mk_err!("read", Pure, Reads),
(None, false, true) => mk_err!("write", Pure, Writes),
(None, true, true) => mk_err!("read & write", Pure, ReadsWrites),
(Some(StorageOperation::Reads), _, true) => mk_err!("write", Reads, Writes),
(Some(StorageOperation::Writes), true, _) => mk_err!("read", Writes, Reads),
(Some(StorageOperation::ReadsWrites), false, true) => mk_warn!(Reads),
(Some(StorageOperation::ReadsWrites), true, false) => mk_warn!(Writes),
(Some(StorageOperation::Reads), false, false) => mk_warn!(Reads),
(Some(StorageOperation::Writes), false, false) => mk_warn!(Writes),
_otherwise => (),
};
(reads, writes)
}
pub(crate) fn results(self) -> CompileResult<()> {
if self.errors.is_empty() {
ok((), self.warnings, self.errors)
} else {
err(self.warnings, self.errors)
}
}
}