sui_adapter_latest/static_programmable_transactions/typing/verify/
drop_safety.rs

1// Copyright (c) Mysten Labs, Inc.
2// SPDX-License-Identifier: Apache-2.0
3
4use crate::{
5    execution_mode::ExecutionMode,
6    static_programmable_transactions::{env::Env, typing::ast as T},
7};
8use sui_types::error::ExecutionError;
9
10/// Refines usage of values so that the last `Copy` of a value is a `Move` if it is not borrowed
11/// After, it verifies the following
12/// - No results without `drop` are unused (all unused non-input values have `drop`)
13pub fn refine_and_verify<Mode: ExecutionMode>(
14    env: &Env,
15    ast: &mut T::Transaction,
16) -> Result<(), ExecutionError> {
17    refine::transaction(ast);
18    verify::transaction::<Mode>(env, ast)?;
19    Ok(())
20}
21
22mod refine {
23    use crate::{
24        sp,
25        static_programmable_transactions::typing::ast::{self as T},
26    };
27    use std::collections::BTreeSet;
28
29    /// After memory safety, we can switch the last usage of a `Copy` to a `Move` if it is not
30    /// borrowed at the time of the last usage.
31    pub fn transaction(ast: &mut T::Transaction) {
32        let mut used: BTreeSet<T::Location> = BTreeSet::new();
33        for c in ast.commands.iter_mut().rev() {
34            command(&mut used, c);
35        }
36    }
37
38    fn command(used: &mut BTreeSet<T::Location>, sp!(_, c): &mut T::Command) {
39        match &mut c.command {
40            T::Command__::MoveCall(mc) => arguments(used, &mut mc.arguments),
41            T::Command__::TransferObjects(objects, recipient) => {
42                argument(used, recipient);
43                arguments(used, objects);
44            }
45            T::Command__::SplitCoins(_, coin, amounts) => {
46                arguments(used, amounts);
47                argument(used, coin);
48            }
49            T::Command__::MergeCoins(_, target, coins) => {
50                arguments(used, coins);
51                argument(used, target);
52            }
53            T::Command__::MakeMoveVec(_, xs) => arguments(used, xs),
54            T::Command__::Publish(_, _, _) => (),
55            T::Command__::Upgrade(_, _, _, x, _) => argument(used, x),
56        }
57    }
58
59    fn arguments(used: &mut BTreeSet<T::Location>, args: &mut [T::Argument]) {
60        for arg in args.iter_mut().rev() {
61            argument(used, arg)
62        }
63    }
64
65    fn argument(used: &mut BTreeSet<T::Location>, arg: &mut T::Argument) {
66        let usage = match &mut arg.value.0 {
67            T::Argument__::Use(u) | T::Argument__::Read(u) | T::Argument__::Freeze(u) => u,
68            T::Argument__::Borrow(_, loc) => {
69                // mark location as used
70                used.insert(*loc);
71                return;
72            }
73        };
74        match &usage {
75            T::Usage::Move(loc) => {
76                // mark location as used
77                used.insert(*loc);
78            }
79            T::Usage::Copy { location, borrowed } => {
80                // we are at the last usage of a reference result if it was not yet added to the set
81                let location = *location;
82                let last_usage = used.insert(location);
83                if last_usage && !borrowed.get().unwrap() {
84                    // if it was the last usage, we need to change the Copy to a Move
85                    *usage = T::Usage::Move(location);
86                }
87            }
88        }
89    }
90}
91
92mod verify {
93    use crate::{
94        execution_mode::ExecutionMode,
95        sp,
96        static_programmable_transactions::{
97            env::Env,
98            typing::ast::{self as T, Type},
99        },
100    };
101    use sui_types::error::{ExecutionError, ExecutionErrorKind};
102
103    #[must_use]
104    struct Value;
105
106    struct Context {
107        tx_context: Option<Value>,
108        gas_coin: Option<Value>,
109        objects: Vec<Option<Value>>,
110        pure: Vec<Option<Value>>,
111        receiving: Vec<Option<Value>>,
112        results: Vec<Vec<Option<Value>>>,
113    }
114
115    impl Context {
116        fn new(ast: &T::Transaction) -> Result<Self, ExecutionError> {
117            let objects = ast.objects.iter().map(|_| Some(Value)).collect::<Vec<_>>();
118            let pure = ast.pure.iter().map(|_| Some(Value)).collect::<Vec<_>>();
119            let receiving = ast
120                .receiving
121                .iter()
122                .map(|_| Some(Value))
123                .collect::<Vec<_>>();
124            Ok(Self {
125                tx_context: Some(Value),
126                gas_coin: Some(Value),
127                objects,
128                pure,
129                receiving,
130                results: Vec::with_capacity(ast.commands.len()),
131            })
132        }
133
134        fn location(&mut self, l: T::Location) -> &mut Option<Value> {
135            match l {
136                T::Location::TxContext => &mut self.tx_context,
137                T::Location::GasCoin => &mut self.gas_coin,
138                T::Location::ObjectInput(i) => &mut self.objects[i as usize],
139                T::Location::PureInput(i) => &mut self.pure[i as usize],
140                T::Location::ReceivingInput(i) => &mut self.receiving[i as usize],
141                T::Location::Result(i, j) => &mut self.results[i as usize][j as usize],
142            }
143        }
144    }
145
146    /// Checks the following
147    /// - All unused result values have `drop`
148    pub fn transaction<Mode: ExecutionMode>(
149        _env: &Env,
150        ast: &T::Transaction,
151    ) -> Result<(), ExecutionError> {
152        let mut context = Context::new(ast)?;
153        let commands = &ast.commands;
154        for c in commands {
155            let result =
156                command(&mut context, c).map_err(|e| e.with_command_index(c.idx as usize))?;
157            assert_invariant!(
158                result.len() == c.value.result_type.len(),
159                "result length mismatch"
160            );
161            // drop unused result values
162            assert_invariant!(
163                result.len() == c.value.drop_values.len(),
164                "drop values length mismatch"
165            );
166            let result_values = result
167                .into_iter()
168                .zip(c.value.drop_values.iter().copied())
169                .map(|(v, drop)| {
170                    if !drop {
171                        Some(v)
172                    } else {
173                        consume_value(v);
174                        None
175                    }
176                })
177                .collect();
178            context.results.push(result_values);
179        }
180
181        let Context {
182            tx_context,
183            gas_coin,
184            objects,
185            pure,
186            receiving,
187            results,
188        } = context;
189        consume_value_opt(gas_coin);
190        // TODO do we want to check inputs in the dev inspect case?
191        consume_value_opts(objects);
192        consume_value_opts(pure);
193        consume_value_opts(receiving);
194        assert_invariant!(results.len() == commands.len(), "result length mismatch");
195        for (i, (result, c)) in results.into_iter().zip(&ast.commands).enumerate() {
196            let tys = &c.value.result_type;
197            assert_invariant!(result.len() == tys.len(), "result length mismatch");
198            for (j, (vopt, ty)) in result.into_iter().zip(tys).enumerate() {
199                drop_value_opt::<Mode>((i, j), vopt, ty)?;
200            }
201        }
202        assert_invariant!(tx_context.is_some(), "tx_context should never be moved");
203        Ok(())
204    }
205
206    fn command(
207        context: &mut Context,
208        sp!(_, c): &T::Command,
209    ) -> Result<Vec<Value>, ExecutionError> {
210        let result_tys = &c.result_type;
211        Ok(match &c.command {
212            T::Command__::MoveCall(mc) => {
213                let T::MoveCall {
214                    function,
215                    arguments: args,
216                } = &**mc;
217                let return_ = &function.signature.return_;
218                let arg_values = arguments(context, args)?;
219                consume_values(arg_values);
220                (0..return_.len()).map(|_| Value).collect()
221            }
222            T::Command__::TransferObjects(objects, recipient) => {
223                let object_values = arguments(context, objects)?;
224                let recipient_value = argument(context, recipient)?;
225                consume_values(object_values);
226                consume_value(recipient_value);
227                vec![]
228            }
229            T::Command__::SplitCoins(_, coin, amounts) => {
230                let coin_value = argument(context, coin)?;
231                let amount_values = arguments(context, amounts)?;
232                consume_values(amount_values);
233                consume_value(coin_value);
234                (0..amounts.len()).map(|_| Value).collect()
235            }
236            T::Command__::MergeCoins(_, target, coins) => {
237                let target_value = argument(context, target)?;
238                let coin_values = arguments(context, coins)?;
239                consume_values(coin_values);
240                consume_value(target_value);
241                vec![]
242            }
243            T::Command__::MakeMoveVec(_, xs) => {
244                let vs = arguments(context, xs)?;
245                consume_values(vs);
246                vec![Value]
247            }
248            T::Command__::Publish(_, _, _) => result_tys.iter().map(|_| Value).collect(),
249            T::Command__::Upgrade(_, _, _, x, _) => {
250                let v = argument(context, x)?;
251                consume_value(v);
252                vec![Value]
253            }
254        })
255    }
256
257    fn consume_values(_: Vec<Value>) {}
258
259    fn consume_value(_: Value) {}
260
261    fn consume_value_opts(_: Vec<Option<Value>>) {}
262
263    fn consume_value_opt(_: Option<Value>) {}
264
265    fn drop_value_opt<Mode: ExecutionMode>(
266        idx: (usize, usize),
267        value: Option<Value>,
268        ty: &Type,
269    ) -> Result<(), ExecutionError> {
270        match value {
271            Some(v) => drop_value::<Mode>(idx, v, ty),
272            None => Ok(()),
273        }
274    }
275
276    fn drop_value<Mode: ExecutionMode>(
277        (i, j): (usize, usize),
278        value: Value,
279        ty: &Type,
280    ) -> Result<(), ExecutionError> {
281        let abilities = ty.abilities();
282        if !abilities.has_drop() && !Mode::allow_arbitrary_values() {
283            let msg = if abilities.has_copy() {
284                "The value has copy, but not drop. \
285                Its last usage must be by-value so it can be taken."
286            } else {
287                "Unused value without drop"
288            };
289            return Err(ExecutionError::new_with_source(
290                ExecutionErrorKind::UnusedValueWithoutDrop {
291                    result_idx: i as u16,
292                    secondary_idx: j as u16,
293                },
294                msg,
295            ));
296        }
297        consume_value(value);
298        Ok(())
299    }
300
301    fn arguments(context: &mut Context, xs: &[T::Argument]) -> Result<Vec<Value>, ExecutionError> {
302        xs.iter().map(|x| argument(context, x)).collect()
303    }
304
305    fn argument(context: &mut Context, sp!(_, x): &T::Argument) -> Result<Value, ExecutionError> {
306        match &x.0 {
307            T::Argument__::Use(T::Usage::Move(location)) => move_value(context, *location),
308            T::Argument__::Use(T::Usage::Copy { location, .. }) => copy_value(context, *location),
309            T::Argument__::Borrow(_, location) => borrow_location(context, *location),
310            T::Argument__::Read(usage) => read_ref(context, usage),
311            T::Argument__::Freeze(usage) => freeze_ref(context, usage),
312        }
313    }
314
315    fn move_value(context: &mut Context, l: T::Location) -> Result<Value, ExecutionError> {
316        let Some(value) = context.location(l).take() else {
317            invariant_violation!("memory safety should have failed")
318        };
319        Ok(value)
320    }
321
322    fn copy_value(context: &mut Context, l: T::Location) -> Result<Value, ExecutionError> {
323        assert_invariant!(
324            context.location(l).is_some(),
325            "memory safety should have failed"
326        );
327        Ok(Value)
328    }
329
330    fn borrow_location(context: &mut Context, l: T::Location) -> Result<Value, ExecutionError> {
331        assert_invariant!(
332            context.location(l).is_some(),
333            "memory safety should have failed"
334        );
335        Ok(Value)
336    }
337
338    fn read_ref(context: &mut Context, u: &T::Usage) -> Result<Value, ExecutionError> {
339        let value = match u {
340            T::Usage::Move(l) => move_value(context, *l)?,
341            T::Usage::Copy { location, .. } => copy_value(context, *location)?,
342        };
343        consume_value(value);
344        Ok(Value)
345    }
346
347    fn freeze_ref(context: &mut Context, u: &T::Usage) -> Result<Value, ExecutionError> {
348        let value = match u {
349            T::Usage::Move(l) => move_value(context, *l)?,
350            T::Usage::Copy { location, .. } => copy_value(context, *location)?,
351        };
352        consume_value(value);
353        Ok(Value)
354    }
355}