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        withdrawals: Vec<Option<Value>>,
111        pure: Vec<Option<Value>>,
112        receiving: Vec<Option<Value>>,
113        results: Vec<Vec<Option<Value>>>,
114    }
115
116    impl Context {
117        fn new(ast: &T::Transaction) -> Result<Self, ExecutionError> {
118            let objects = ast.objects.iter().map(|_| Some(Value)).collect::<Vec<_>>();
119            let withdrawals = ast
120                .withdrawals
121                .iter()
122                .map(|_| Some(Value))
123                .collect::<Vec<_>>();
124            let pure = ast.pure.iter().map(|_| Some(Value)).collect::<Vec<_>>();
125            let receiving = ast
126                .receiving
127                .iter()
128                .map(|_| Some(Value))
129                .collect::<Vec<_>>();
130            Ok(Self {
131                tx_context: Some(Value),
132                gas_coin: Some(Value),
133                objects,
134                withdrawals,
135                pure,
136                receiving,
137                results: Vec::with_capacity(ast.commands.len()),
138            })
139        }
140
141        fn location(&mut self, l: T::Location) -> &mut Option<Value> {
142            match l {
143                T::Location::TxContext => &mut self.tx_context,
144                T::Location::GasCoin => &mut self.gas_coin,
145                T::Location::ObjectInput(i) => &mut self.objects[i as usize],
146                T::Location::WithdrawalInput(i) => &mut self.withdrawals[i as usize],
147                T::Location::PureInput(i) => &mut self.pure[i as usize],
148                T::Location::ReceivingInput(i) => &mut self.receiving[i as usize],
149                T::Location::Result(i, j) => &mut self.results[i as usize][j as usize],
150            }
151        }
152    }
153
154    /// Checks the following
155    /// - All unused result values have `drop`
156    pub fn transaction<Mode: ExecutionMode>(
157        _env: &Env,
158        ast: &T::Transaction,
159    ) -> Result<(), ExecutionError> {
160        let mut context = Context::new(ast)?;
161        let commands = &ast.commands;
162        for c in commands {
163            let result =
164                command(&mut context, c).map_err(|e| e.with_command_index(c.idx as usize))?;
165            assert_invariant!(
166                result.len() == c.value.result_type.len(),
167                "result length mismatch"
168            );
169            // drop unused result values
170            assert_invariant!(
171                result.len() == c.value.drop_values.len(),
172                "drop values length mismatch"
173            );
174            let result_values = result
175                .into_iter()
176                .zip(c.value.drop_values.iter().copied())
177                .map(|(v, drop)| {
178                    if !drop {
179                        Some(v)
180                    } else {
181                        consume_value(v);
182                        None
183                    }
184                })
185                .collect();
186            context.results.push(result_values);
187        }
188
189        let Context {
190            tx_context,
191            gas_coin,
192            objects,
193            withdrawals,
194            pure,
195            receiving,
196            results,
197        } = context;
198        consume_value_opt(gas_coin);
199        // TODO do we want to check inputs in the dev inspect case?
200        consume_value_opts(objects);
201        consume_value_opts(withdrawals);
202        consume_value_opts(pure);
203        consume_value_opts(receiving);
204        assert_invariant!(results.len() == commands.len(), "result length mismatch");
205        for (i, (result, c)) in results.into_iter().zip(&ast.commands).enumerate() {
206            let tys = &c.value.result_type;
207            assert_invariant!(result.len() == tys.len(), "result length mismatch");
208            for (j, (vopt, ty)) in result.into_iter().zip(tys).enumerate() {
209                drop_value_opt::<Mode>((i, j), vopt, ty)?;
210            }
211        }
212        assert_invariant!(tx_context.is_some(), "tx_context should never be moved");
213        Ok(())
214    }
215
216    fn command(
217        context: &mut Context,
218        sp!(_, c): &T::Command,
219    ) -> Result<Vec<Value>, ExecutionError> {
220        let result_tys = &c.result_type;
221        Ok(match &c.command {
222            T::Command__::MoveCall(mc) => {
223                let T::MoveCall {
224                    function,
225                    arguments: args,
226                } = &**mc;
227                let return_ = &function.signature.return_;
228                let arg_values = arguments(context, args)?;
229                consume_values(arg_values);
230                (0..return_.len()).map(|_| Value).collect()
231            }
232            T::Command__::TransferObjects(objects, recipient) => {
233                let object_values = arguments(context, objects)?;
234                let recipient_value = argument(context, recipient)?;
235                consume_values(object_values);
236                consume_value(recipient_value);
237                vec![]
238            }
239            T::Command__::SplitCoins(_, coin, amounts) => {
240                let coin_value = argument(context, coin)?;
241                let amount_values = arguments(context, amounts)?;
242                consume_values(amount_values);
243                consume_value(coin_value);
244                (0..amounts.len()).map(|_| Value).collect()
245            }
246            T::Command__::MergeCoins(_, target, coins) => {
247                let target_value = argument(context, target)?;
248                let coin_values = arguments(context, coins)?;
249                consume_values(coin_values);
250                consume_value(target_value);
251                vec![]
252            }
253            T::Command__::MakeMoveVec(_, xs) => {
254                let vs = arguments(context, xs)?;
255                consume_values(vs);
256                vec![Value]
257            }
258            T::Command__::Publish(_, _, _) => result_tys.iter().map(|_| Value).collect(),
259            T::Command__::Upgrade(_, _, _, x, _) => {
260                let v = argument(context, x)?;
261                consume_value(v);
262                vec![Value]
263            }
264        })
265    }
266
267    fn consume_values(_: Vec<Value>) {}
268
269    fn consume_value(_: Value) {}
270
271    fn consume_value_opts(_: Vec<Option<Value>>) {}
272
273    fn consume_value_opt(_: Option<Value>) {}
274
275    fn drop_value_opt<Mode: ExecutionMode>(
276        idx: (usize, usize),
277        value: Option<Value>,
278        ty: &Type,
279    ) -> Result<(), ExecutionError> {
280        match value {
281            Some(v) => drop_value::<Mode>(idx, v, ty),
282            None => Ok(()),
283        }
284    }
285
286    fn drop_value<Mode: ExecutionMode>(
287        (i, j): (usize, usize),
288        value: Value,
289        ty: &Type,
290    ) -> Result<(), ExecutionError> {
291        let abilities = ty.abilities();
292        if !abilities.has_drop() && !Mode::allow_arbitrary_values() {
293            let msg = if abilities.has_copy() {
294                "The value has copy, but not drop. \
295                Its last usage must be by-value so it can be taken."
296            } else {
297                "Unused value without drop"
298            };
299            return Err(ExecutionError::new_with_source(
300                ExecutionErrorKind::UnusedValueWithoutDrop {
301                    result_idx: i as u16,
302                    secondary_idx: j as u16,
303                },
304                msg,
305            ));
306        }
307        consume_value(value);
308        Ok(())
309    }
310
311    fn arguments(context: &mut Context, xs: &[T::Argument]) -> Result<Vec<Value>, ExecutionError> {
312        xs.iter().map(|x| argument(context, x)).collect()
313    }
314
315    fn argument(context: &mut Context, sp!(_, x): &T::Argument) -> Result<Value, ExecutionError> {
316        match &x.0 {
317            T::Argument__::Use(T::Usage::Move(location)) => move_value(context, *location),
318            T::Argument__::Use(T::Usage::Copy { location, .. }) => copy_value(context, *location),
319            T::Argument__::Borrow(_, location) => borrow_location(context, *location),
320            T::Argument__::Read(usage) => read_ref(context, usage),
321            T::Argument__::Freeze(usage) => freeze_ref(context, usage),
322        }
323    }
324
325    fn move_value(context: &mut Context, l: T::Location) -> Result<Value, ExecutionError> {
326        let Some(value) = context.location(l).take() else {
327            invariant_violation!("memory safety should have failed")
328        };
329        Ok(value)
330    }
331
332    fn copy_value(context: &mut Context, l: T::Location) -> Result<Value, ExecutionError> {
333        assert_invariant!(
334            context.location(l).is_some(),
335            "memory safety should have failed"
336        );
337        Ok(Value)
338    }
339
340    fn borrow_location(context: &mut Context, l: T::Location) -> Result<Value, ExecutionError> {
341        assert_invariant!(
342            context.location(l).is_some(),
343            "memory safety should have failed"
344        );
345        Ok(Value)
346    }
347
348    fn read_ref(context: &mut Context, u: &T::Usage) -> Result<Value, ExecutionError> {
349        let value = match u {
350            T::Usage::Move(l) => move_value(context, *l)?,
351            T::Usage::Copy { location, .. } => copy_value(context, *location)?,
352        };
353        consume_value(value);
354        Ok(Value)
355    }
356
357    fn freeze_ref(context: &mut Context, u: &T::Usage) -> Result<Value, ExecutionError> {
358        let value = match u {
359            T::Usage::Move(l) => move_value(context, *l)?,
360            T::Usage::Copy { location, .. } => copy_value(context, *location)?,
361        };
362        consume_value(value);
363        Ok(Value)
364    }
365}