neo_solidity/ir/build/module/
hazards.rs1#[derive(Default, Clone, Copy)]
2struct Hazards {
3 writes_state: bool,
4 notifies: bool,
5 unsafe_contract_call: bool,
6 reads_state: bool,
7 reads_environment: bool,
8 contract_calls: bool,
9}
10
11impl Hazards {
12 fn safe_violation(self) -> bool {
13 self.writes_state || self.notifies || self.unsafe_contract_call
14 }
15
16 fn pure_violation(self) -> bool {
17 self.safe_violation()
18 || self.reads_state
19 || self.reads_environment
20 || self.contract_calls
21 }
22
23 fn merge_in(&mut self, other: Hazards) {
24 self.writes_state |= other.writes_state;
25 self.notifies |= other.notifies;
26 self.unsafe_contract_call |= other.unsafe_contract_call;
27 self.reads_state |= other.reads_state;
28 self.reads_environment |= other.reads_environment;
29 self.contract_calls |= other.contract_calls;
30 }
31}
32
33fn direct_hazards(function: &Function) -> Hazards {
34 let instrs: Vec<&Instruction> = function
35 .basic_blocks
36 .iter()
37 .flat_map(|block| block.instructions.iter())
38 .collect();
39
40 let mut hazards = Hazards::default();
41
42 for (index, instr) in instrs.iter().enumerate() {
43 match instr {
44 Instruction::LoadState(_)
45 | Instruction::LoadMappingElement { .. }
46 | Instruction::LoadStructField { .. }
47 | Instruction::LoadStructArrayElement { .. }
48 | Instruction::LoadStorageDynamic => {
49 hazards.reads_state = true;
50 }
51 Instruction::StoreState(_)
52 | Instruction::StoreMappingElement { .. }
53 | Instruction::StoreStructField { .. }
54 | Instruction::StoreStructArrayElement { .. } => {
55 hazards.writes_state = true;
56 }
57 Instruction::LoadRuntimeValue(_) => {
58 hazards.reads_environment = true;
59 }
60 Instruction::EmitEvent { .. } | Instruction::EmitEventByName { .. } => {
61 hazards.notifies = true;
62 }
63 Instruction::CallBuiltin { builtin, arg_count } => match builtin {
64 BuiltinCall::StorageGet | BuiltinCall::StorageFind => {
65 hazards.reads_state = true;
66 }
67 BuiltinCall::StoragePut | BuiltinCall::StorageDelete => {
68 hazards.writes_state = true;
69 }
70 BuiltinCall::RuntimeNotify | BuiltinCall::NotifySerialized => {
71 hazards.notifies = true;
72 }
73 BuiltinCall::RuntimeCheckWitness => {
74 hazards.reads_environment = true;
75 }
76 BuiltinCall::DeployContract => {
77 hazards.writes_state = true;
78 hazards.contract_calls = true;
79 }
80 BuiltinCall::GetContract
81 | BuiltinCall::GetContractScript
82 | BuiltinCall::GetNeoAccountState => {
83 hazards.contract_calls = true;
84 }
85 BuiltinCall::NativeCall { contract, method } => {
86 if native_call_is_mutating(*contract, method) {
87 hazards.writes_state = true;
88 }
89 if !matches!(contract, NativeContract::CryptoLib | NativeContract::StdLib) {
90 hazards.contract_calls = true;
91 }
92 }
93 BuiltinCall::Syscall(name) => {
94 if matches!(
96 name.as_str(),
97 "System.Storage.Put"
98 | "System.Storage.Delete"
99 | "System.Storage.Local.Put"
100 | "System.Storage.Local.Delete"
101 ) {
102 hazards.writes_state = true;
103 }
104 if matches!(name.as_str(), "System.Runtime.Notify" | "System.Runtime.Log") {
105 hazards.notifies = true;
106 }
107
108 if matches!(
110 name.as_str(),
111 "System.Storage.Get"
112 | "System.Storage.Find"
113 | "System.Storage.GetContext"
114 | "System.Storage.GetReadOnlyContext"
115 | "System.Storage.Local.GetContext"
116 | "System.Storage.Local.GetReadOnlyContext"
117 ) {
118 hazards.reads_state = true;
119 }
120
121 if name.starts_with("System.Runtime.")
123 && !matches!(
124 name.as_str(),
125 "System.Runtime.Notify" | "System.Runtime.Log"
126 )
127 {
128 hazards.reads_environment = true;
129 }
130
131 if matches!(name.as_str(), "System.Iterator.Next" | "System.Iterator.Value") {
133 hazards.reads_state = true;
134 }
135
136 if name == "System.Contract.Call" {
139 hazards.contract_calls = true;
140 }
141 if name == "System.Contract.Call" && *arg_count == 4 {
142 let flags = match instrs.get(index.wrapping_sub(3)) {
143 Some(Instruction::PushLiteral(LiteralValue::Integer(value))) => {
144 parse_u8_literal(value)
145 }
146 _ => None,
147 };
148 if flags.is_none_or(call_flags_allow_write_or_notify) {
149 hazards.unsafe_contract_call = true;
150 }
151 }
152 }
153 BuiltinCall::ContractCall => {
154 hazards.contract_calls = true;
156 hazards.unsafe_contract_call = true;
157 }
158 BuiltinCall::ContractCallWithFlags => {
159 hazards.contract_calls = true;
160 let flags = match instrs.get(index.wrapping_sub(1)) {
161 Some(Instruction::PushLiteral(LiteralValue::Integer(value))) => {
162 parse_u8_literal(value)
163 }
164 _ => None,
165 };
166 if flags.is_none_or(call_flags_allow_write_or_notify) {
167 hazards.unsafe_contract_call = true;
168 }
169 }
170 _ => {}
171 },
172 _ => {}
173 }
174 }
175
176 hazards
177}
178
179fn build_call_graph(functions: &[Function]) -> HashMap<String, Vec<String>> {
180 let mut graph: HashMap<String, Vec<String>> = HashMap::new();
181 for function in functions {
182 let mut callees: Vec<String> = Vec::new();
183 for instr in function
184 .basic_blocks
185 .iter()
186 .flat_map(|block| block.instructions.iter())
187 {
188 if let Instruction::CallFunction { name, .. } = instr {
189 callees.push(name.clone());
190 }
191 }
192 graph.insert(function.name.clone(), callees);
193 }
194 graph
195}
196
197fn compute_transitive_hazards(
198 name: &str,
199 direct: &HashMap<String, Hazards>,
200 graph: &HashMap<String, Vec<String>>,
201 memo: &mut HashMap<String, Hazards>,
202 visiting: &mut HashSet<String>,
203) -> Hazards {
204 if let Some(value) = memo.get(name).copied() {
205 return value;
206 }
207
208 if visiting.contains(name) {
209 return direct.get(name).copied().unwrap_or_default();
210 }
211
212 visiting.insert(name.to_string());
213
214 let mut hazards = direct.get(name).copied().unwrap_or_default();
215 if let Some(callees) = graph.get(name) {
216 for callee in callees {
217 let child = compute_transitive_hazards(callee, direct, graph, memo, visiting);
218 hazards.merge_in(child);
219 }
220 }
221
222 visiting.remove(name);
223 memo.insert(name.to_string(), hazards);
224 hazards
225}