1use proc_macro::TokenStream;
5use quote::{ToTokens, quote, quote_spanned};
6use syn::{
7 Attribute, BinOp, Data, DataEnum, DeriveInput, Expr, ExprBinary, ExprMacro, Item, ItemMacro,
8 Stmt, StmtMacro, Token, UnOp,
9 fold::{Fold, fold_expr, fold_item_macro, fold_stmt},
10 parse::Parser,
11 parse_macro_input, parse2,
12 punctuated::Punctuated,
13 spanned::Spanned,
14};
15
16#[proc_macro_attribute]
21pub fn sui_test(args: TokenStream, item: TokenStream) -> TokenStream {
22 let input = parse_macro_input!(item as syn::ItemFn);
23 let arg_parser = Punctuated::<syn::Meta, Token![,]>::parse_terminated;
24 let args = arg_parser.parse(args).unwrap().into_iter();
25
26 let header = if cfg!(msim) {
27 quote! {
28 #[::sui_simulator::sim_test(crate = "sui_simulator", #(#args)* )]
29 }
30 } else {
31 quote! {
32 #[::tokio::test(#(#args)*)]
33 }
34 };
35
36 let result = quote! {
37 #header
38 #input
39 };
40
41 result.into()
42}
43
44#[proc_macro_attribute]
51pub fn sim_test(args: TokenStream, item: TokenStream) -> TokenStream {
52 let input = parse_macro_input!(item as syn::ItemFn);
53 let arg_parser = Punctuated::<syn::Meta, Token![,]>::parse_terminated;
54 let args = arg_parser.parse(args).unwrap().into_iter();
55
56 let ignore = input
57 .attrs
58 .iter()
59 .find(|attr| attr.path().is_ident("ignore"))
60 .map_or(quote! {}, |_| quote! { #[ignore] });
61
62 let result = if cfg!(msim) {
63 let sig = &input.sig;
64 let return_type = &sig.output;
65 let body = &input.block;
66 quote! {
67 #[::sui_simulator::sim_test(crate = "sui_simulator", #(#args),*)]
68 #ignore
69 #sig {
70 ::sui_simulator::telemetry_subscribers::init_for_testing();
71
72 async fn body_fn() #return_type { #body }
73
74 let timeout_secs: u64 = std::env::var("SUI_SIM_TEST_TIMEOUT_SECS")
75 .ok()
76 .and_then(|s| s.parse().ok())
77 .unwrap_or(1000);
78 let timeout_duration = tokio::time::Duration::from_secs(timeout_secs);
79
80 let ret = tokio::time::timeout(timeout_duration, body_fn())
81 .await
82 .expect("sim_test timed out");
83
84 ::sui_simulator::task::shutdown_all_nodes();
85
86 tokio::time::sleep(tokio::time::Duration::from_millis(10)).await;
89
90 assert_eq!(
91 sui_simulator::NodeLeakDetector::get_current_node_count(),
92 0,
93 "SuiNode leak detected"
94 );
95
96 ret
97 }
98 }
99 } else {
100 let fn_name = &input.sig.ident;
101 let sig = &input.sig;
102 let body = &input.block;
103 quote! {
104 #[allow(clippy::needless_return)]
105 #[tokio::test]
106 #ignore
107 #sig {
108 if std::env::var("SUI_SKIP_SIMTESTS").is_ok() {
109 println!("not running test {} in `cargo test`: SUI_SKIP_SIMTESTS is set", stringify!(#fn_name));
110
111 struct Ret;
112
113 impl From<Ret> for () {
114 fn from(_ret: Ret) -> Self {
115 }
116 }
117
118 impl<E> From<Ret> for Result<(), E> {
119 fn from(_ret: Ret) -> Self {
120 Ok(())
121 }
122 }
123
124 return Ret.into();
125 }
126
127 #body
128 }
129 }
130 };
131
132 result.into()
133}
134
135#[proc_macro]
136pub fn checked_arithmetic(input: TokenStream) -> TokenStream {
137 let input_file = CheckArithmetic.fold_file(parse_macro_input!(input));
138
139 let output_items = input_file.items;
140
141 let output = quote! {
142 #(#output_items)*
143 };
144
145 TokenStream::from(output)
146}
147
148#[proc_macro_attribute]
149pub fn with_checked_arithmetic(_attr: TokenStream, item: TokenStream) -> TokenStream {
150 let input_item = parse_macro_input!(item as Item);
151 match input_item {
152 Item::Fn(input_fn) => {
153 let transformed_fn = CheckArithmetic.fold_item_fn(input_fn);
154 TokenStream::from(quote! { #transformed_fn })
155 }
156 Item::Impl(input_impl) => {
157 let transformed_impl = CheckArithmetic.fold_item_impl(input_impl);
158 TokenStream::from(quote! { #transformed_impl })
159 }
160 item => {
161 let transformed_impl = CheckArithmetic.fold_item(item);
162 TokenStream::from(quote! { #transformed_impl })
163 }
164 }
165}
166
167struct CheckArithmetic;
168
169impl CheckArithmetic {
170 fn maybe_skip_macro(&self, attrs: &mut Vec<Attribute>) -> bool {
171 if let Some(idx) = attrs
172 .iter()
173 .position(|attr| attr.path().is_ident("skip_checked_arithmetic"))
174 {
175 attrs.remove(idx);
178 true
179 } else {
180 false
181 }
182 }
183
184 fn process_macro_contents(
185 &mut self,
186 tokens: proc_macro2::TokenStream,
187 ) -> syn::Result<proc_macro2::TokenStream> {
188 let parser = Punctuated::<Expr, Token![,]>::parse_terminated;
190 let Ok(exprs) = parser.parse(tokens.clone().into()) else {
191 return Err(syn::Error::new_spanned(
192 tokens,
193 "could not process macro contents - use #[skip_checked_arithmetic] to skip this macro",
194 ));
195 };
196
197 let folded_exprs = exprs
199 .into_iter()
200 .map(|expr| self.fold_expr(expr))
201 .collect::<Vec<_>>();
202
203 let mut folded_tokens = proc_macro2::TokenStream::new();
205 for (i, folded_expr) in folded_exprs.into_iter().enumerate() {
206 if i > 0 {
207 folded_tokens.extend(std::iter::once::<proc_macro2::TokenTree>(
208 proc_macro2::Punct::new(',', proc_macro2::Spacing::Alone).into(),
209 ));
210 }
211 folded_expr.to_tokens(&mut folded_tokens);
212 }
213
214 Ok(folded_tokens)
215 }
216}
217
218impl Fold for CheckArithmetic {
219 fn fold_stmt(&mut self, stmt: Stmt) -> Stmt {
220 let stmt = fold_stmt(self, stmt);
221 if let Stmt::Macro(stmt_macro) = stmt {
222 let StmtMacro {
223 mut attrs,
224 mut mac,
225 semi_token,
226 } = stmt_macro;
227
228 if self.maybe_skip_macro(&mut attrs) {
229 Stmt::Macro(StmtMacro {
230 attrs,
231 mac,
232 semi_token,
233 })
234 } else {
235 match self.process_macro_contents(mac.tokens.clone()) {
236 Ok(folded_tokens) => {
237 mac.tokens = folded_tokens;
238 Stmt::Macro(StmtMacro {
239 attrs,
240 mac,
241 semi_token,
242 })
243 }
244 Err(error) => parse2(error.to_compile_error()).unwrap(),
245 }
246 }
247 } else {
248 stmt
249 }
250 }
251
252 fn fold_item_macro(&mut self, mut item_macro: ItemMacro) -> ItemMacro {
253 if !self.maybe_skip_macro(&mut item_macro.attrs) {
254 let err = syn::Error::new_spanned(
255 item_macro.to_token_stream(),
256 "cannot process macros - use #[skip_checked_arithmetic] to skip \
257 processing this macro",
258 );
259
260 return parse2(err.to_compile_error()).unwrap();
261 }
262 fold_item_macro(self, item_macro)
263 }
264
265 fn fold_expr(&mut self, expr: Expr) -> Expr {
266 let span = expr.span();
267 let expr = fold_expr(self, expr);
268 let expr = match expr {
269 Expr::Macro(expr_macro) => {
270 let ExprMacro { mut attrs, mut mac } = expr_macro;
271
272 if self.maybe_skip_macro(&mut attrs) {
273 return Expr::Macro(ExprMacro { attrs, mac });
274 } else {
275 match self.process_macro_contents(mac.tokens.clone()) {
276 Ok(folded_tokens) => {
277 mac.tokens = folded_tokens;
278 let expr_macro = Expr::Macro(ExprMacro { attrs, mac });
279 quote!(#expr_macro)
280 }
281 Err(error) => {
282 return Expr::Verbatim(error.to_compile_error());
283 }
284 }
285 }
286 }
287
288 Expr::Binary(expr_binary) => {
289 let ExprBinary {
290 attrs,
291 mut left,
292 op,
293 mut right,
294 } = expr_binary;
295
296 fn remove_parens(expr: &mut Expr) {
297 if let Expr::Paren(paren) = expr {
298 assert!(paren.attrs.is_empty(), "TODO: attrs on parenthesized");
300 *expr = *paren.expr.clone();
301 }
302 }
303
304 macro_rules! wrap_op {
305 ($left: expr, $right: expr, $method: ident, $span: expr) => {{
306 remove_parens(&mut $left);
309 remove_parens(&mut $right);
310
311 quote_spanned!($span => {
312 let (left, right) = (#left, #right);
315 left.$method(right)
316 .unwrap_or_else(||
317 panic!(
318 "Overflow or underflow in {} {} + {}",
319 stringify!($method),
320 left,
321 right,
322 )
323 )
324 })
325 }};
326 }
327
328 macro_rules! wrap_op_assign {
329 ($left: expr, $right: expr, $method: ident, $span: expr) => {{
330 remove_parens(&mut $left);
333 remove_parens(&mut $right);
334
335 quote_spanned!($span => {
336 let (left, right) = (&mut #left, #right);
339 *left = (*left).$method(right)
340 .unwrap_or_else(||
341 panic!(
342 "Overflow or underflow in {} {} + {}",
343 stringify!($method),
344 *left,
345 right
346 )
347 )
348 })
349 }};
350 }
351
352 match op {
353 BinOp::Add(_) => {
354 wrap_op!(left, right, checked_add, span)
355 }
356 BinOp::Sub(_) => {
357 wrap_op!(left, right, checked_sub, span)
358 }
359 BinOp::Mul(_) => {
360 wrap_op!(left, right, checked_mul, span)
361 }
362 BinOp::Div(_) => {
363 wrap_op!(left, right, checked_div, span)
364 }
365 BinOp::Rem(_) => {
366 wrap_op!(left, right, checked_rem, span)
367 }
368 BinOp::AddAssign(_) => {
369 wrap_op_assign!(left, right, checked_add, span)
370 }
371 BinOp::SubAssign(_) => {
372 wrap_op_assign!(left, right, checked_sub, span)
373 }
374 BinOp::MulAssign(_) => {
375 wrap_op_assign!(left, right, checked_mul, span)
376 }
377 BinOp::DivAssign(_) => {
378 wrap_op_assign!(left, right, checked_div, span)
379 }
380 BinOp::RemAssign(_) => {
381 wrap_op_assign!(left, right, checked_rem, span)
382 }
383 _ => {
384 let expr_binary = ExprBinary {
385 attrs,
386 left,
387 op,
388 right,
389 };
390 quote_spanned!(span => #expr_binary)
391 }
392 }
393 }
394 Expr::Unary(expr_unary) => {
395 let op = &expr_unary.op;
396 let operand = &expr_unary.expr;
397 match op {
398 UnOp::Neg(_) => {
399 quote_spanned!(span => #operand.checked_neg().expect("Overflow or underflow in negation"))
400 }
401 _ => quote_spanned!(span => #expr_unary),
402 }
403 }
404 _ => quote_spanned!(span => #expr),
405 };
406
407 parse2(expr).unwrap()
408 }
409}
410
411#[proc_macro_derive(EnumVariantOrder)]
428pub fn enum_variant_order_derive(input: TokenStream) -> TokenStream {
429 let ast = parse_macro_input!(input as DeriveInput);
430 let name = &ast.ident;
431
432 if let Data::Enum(DataEnum { variants, .. }) = ast.data {
433 let variant_entries = variants
434 .iter()
435 .enumerate()
436 .map(|(index, variant)| {
437 let variant_name = variant.ident.to_string();
438 quote! {
439 map.insert( #index as u64, (#variant_name).to_string());
440 }
441 })
442 .collect::<Vec<_>>();
443
444 let deriv = quote! {
445 impl sui_enum_compat_util::EnumOrderMap for #name {
446 fn order_to_variant_map() -> std::collections::BTreeMap<u64, String > {
447 let mut map = std::collections::BTreeMap::new();
448 #(#variant_entries)*
449 map
450 }
451 }
452 };
453
454 deriv.into()
455 } else {
456 panic!("EnumVariantOrder can only be used with enums.");
457 }
458}