1use super::ReturnBomb;
2use crate::{
3 linter::{LateLintPass, LintContext},
4 sol::{Severity, SolLint, calls::is_call_with_gas_limit},
5};
6use solar::{
7 ast::{ElementaryType, LitKind, StrKind},
8 interface::{Symbol, kw, sym},
9 sema::hir::{self, CallArgs, CallArgsKind, ExprKind, ItemId, TypeKind},
10};
11
12declare_forge_lint!(
13 RETURN_BOMB,
14 Severity::Low,
15 "return-bomb",
16 "external calls with a gas limit should not consume unbounded return data"
17);
18
19impl<'hir> LateLintPass<'hir> for ReturnBomb {
20 fn check_expr(
21 &mut self,
22 ctx: &LintContext,
23 hir: &'hir hir::Hir<'hir>,
24 expr: &'hir hir::Expr<'hir>,
25 ) {
26 if low_level_call_with_gas_consumes_unbounded_return_data(hir, expr)
28 || call_with_gas_returns_dynamic_data(hir, expr)
29 {
30 ctx.emit(&RETURN_BOMB, expr.span);
31 }
32 }
33}
34
35fn call_with_gas_returns_dynamic_data(hir: &hir::Hir<'_>, expr: &hir::Expr<'_>) -> bool {
37 is_call_with_gas_limit(expr) && call_returns_dynamic_data(hir, expr)
38}
39fn call_returns_dynamic_data(hir: &hir::Hir<'_>, expr: &hir::Expr<'_>) -> bool {
41 let ExprKind::Call(callee, args, _) = &expr.peel_parens().kind else { return false };
42 matching_functions_for_callee(hir, callee, args)
43 .is_some_and(|functions| functions_return_dynamic_data(hir, &functions))
44}
45
46fn low_level_call_with_gas_consumes_unbounded_return_data(
48 hir: &hir::Hir<'_>,
49 expr: &hir::Expr<'_>,
50) -> bool {
51 if !is_call_with_gas_limit(expr) {
52 return false;
53 }
54
55 let ExprKind::Call(callee, _, _) = &expr.peel_parens().kind else { return false };
56 let ExprKind::Member(receiver, member) = &callee.peel_parens().kind else { return false };
57 matches!(member.name, kw::Call | kw::Delegatecall | kw::Staticcall)
58 && expr_is_address(hir, receiver)
59}
60fn matching_functions_for_callee<'hir>(
62 hir: &'hir hir::Hir<'hir>,
63 callee: &hir::Expr<'hir>,
64 args: &CallArgs<'hir>,
65) -> Option<FunctionCandidates<'hir>> {
66 let functions = match &callee.peel_parens().kind {
67 ExprKind::Ident(reses) => {
68 let candidates = reses.iter().filter_map(res_function_id);
69 Some(select_functions_for_args(hir, candidates, args))
70 }
71 ExprKind::Member(base, member) => expr_contract_id(hir, base).map(|contract| {
72 let candidates = function_candidates_for_member(hir, contract, member.name);
73 select_functions_for_args(hir, candidates, args)
74 }),
75 _ => None,
76 };
77
78 functions
79 .filter(|functions| !functions.is_empty())
80 .or_else(|| function_pointer_candidates_for_callee(hir, callee, args))
81}
82
83fn function_pointer_candidates_for_callee<'hir>(
85 hir: &'hir hir::Hir<'hir>,
86 callee: &hir::Expr<'hir>,
87 args: &CallArgs<'_>,
88) -> Option<FunctionCandidates<'hir>> {
89 expr_type(hir, callee)
90 .and_then(|ty| function_type_returns_for_args(hir, ty, args))
91 .map(FunctionCandidates::Pointer)
92}
93
94fn function_candidates_for_member<'hir>(
96 hir: &'hir hir::Hir<'hir>,
97 contract: hir::ContractId,
98 member: Symbol,
99) -> impl Iterator<Item = hir::FunctionId> + Clone + 'hir {
100 hir.contract_item_ids(contract).filter_map(move |item| {
101 let ItemId::Function(id) = item else { return None };
102 let function = hir.function(id);
103 (function.name?.name == member && is_externally_callable(function)).then_some(id)
104 })
105}
106
107const fn is_externally_callable(function: &hir::Function<'_>) -> bool {
109 matches!(function.visibility, hir::Visibility::Public | hir::Visibility::External)
110}
111
112fn select_functions_for_args<'hir>(
114 hir: &'hir hir::Hir<'hir>,
115 candidates: impl Iterator<Item = hir::FunctionId>,
116 args: &CallArgs<'hir>,
117) -> FunctionCandidates<'hir> {
118 let mut exact = Vec::new();
119 let mut maybe = Vec::new();
120
121 for id in candidates {
122 match function_arg_match(hir, id, args) {
123 ArgMatch::Exact => exact.push(id),
124 ArgMatch::Maybe => maybe.push(id),
125 ArgMatch::No => {}
126 }
127 }
128
129 if exact.is_empty() {
130 FunctionCandidates::Maybe(maybe)
131 } else {
132 FunctionCandidates::Exact(exact)
133 }
134}
135fn functions_return_dynamic_data(hir: &hir::Hir<'_>, functions: &FunctionCandidates<'_>) -> bool {
137 match functions {
138 FunctionCandidates::Exact(functions) => match functions.as_slice() {
139 [] => false,
140 [function] => function_returns_dynamic_data(hir, *function),
141 [first, rest @ ..] => {
142 let first = function_returns_dynamic_data(hir, *first);
143 rest.iter().all(|&function| function_returns_dynamic_data(hir, function) == first)
144 && first
145 }
146 },
147 FunctionCandidates::Maybe(functions) => {
148 functions.iter().any(|&function| function_returns_dynamic_data(hir, function))
149 }
150 FunctionCandidates::Pointer(returns) => returns_dynamic_data(hir, returns),
151 }
152}
153
154enum FunctionCandidates<'hir> {
155 Exact(Vec<hir::FunctionId>),
156 Maybe(Vec<hir::FunctionId>),
157 Pointer(&'hir [hir::VariableId]),
158}
159
160impl FunctionCandidates<'_> {
161 const fn is_empty(&self) -> bool {
162 match self {
163 Self::Exact(functions) | Self::Maybe(functions) => functions.is_empty(),
164 Self::Pointer(returns) => returns.is_empty(),
165 }
166 }
167}
168
169fn returns_dynamic_data(hir: &hir::Hir<'_>, returns: &[hir::VariableId]) -> bool {
171 returns.iter().any(|&var| is_dynamic_type(hir, &hir.variable(var).ty))
172}
173
174const fn res_function_id(res: &hir::Res) -> Option<hir::FunctionId> {
175 match res {
176 hir::Res::Item(ItemId::Function(id)) => Some(*id),
177 _ => None,
178 }
179}
180
181fn function_returns_dynamic_data(hir: &hir::Hir<'_>, function: hir::FunctionId) -> bool {
183 returns_dynamic_data(hir, hir.function(function).returns)
184}
185
186enum ArgMatch {
187 Exact,
188 Maybe,
189 No,
190}
191fn function_arg_match(hir: &hir::Hir<'_>, id: hir::FunctionId, args: &CallArgs<'_>) -> ArgMatch {
193 let function = hir.function(id);
194 params_arg_match(hir, function.parameters, args)
195}
196
197fn params_arg_match(
199 hir: &hir::Hir<'_>,
200 parameters: &[hir::VariableId],
201 args: &CallArgs<'_>,
202) -> ArgMatch {
203 if args.len() != parameters.len() {
204 return ArgMatch::No;
205 }
206
207 match &args.kind {
208 CallArgsKind::Unnamed(exprs) => {
209 params_match_args(hir, parameters.iter().copied().zip(exprs.iter()))
210 }
211 CallArgsKind::Named(named_args) => {
212 let mut maybe = false;
213 for named_arg in *named_args {
214 let Some(param) = parameters.iter().copied().find(|¶m| {
215 hir.variable(param).name.is_some_and(|name| name.name == named_arg.name.name)
216 }) else {
217 return ArgMatch::No;
218 };
219 match expr_matches_type(hir, &named_arg.value, &hir.variable(param).ty) {
220 Some(true) => {}
221 Some(false) => return ArgMatch::No,
222 None => maybe = true,
223 }
224 }
225 if maybe { ArgMatch::Maybe } else { ArgMatch::Exact }
226 }
227 }
228}
229fn params_match_args<'hir>(
231 hir: &hir::Hir<'hir>,
232 params_and_args: impl Iterator<Item = (hir::VariableId, &'hir hir::Expr<'hir>)>,
233) -> ArgMatch {
234 let mut maybe = false;
235 for (param, arg) in params_and_args {
236 match expr_matches_type(hir, arg, &hir.variable(param).ty) {
237 Some(true) => {}
238 Some(false) => return ArgMatch::No,
239 None => maybe = true,
240 }
241 }
242 if maybe { ArgMatch::Maybe } else { ArgMatch::Exact }
243}
244fn expr_contract_id(hir: &hir::Hir<'_>, expr: &hir::Expr<'_>) -> Option<hir::ContractId> {
246 match &expr.peel_parens().kind {
247 ExprKind::Ident(reses) if is_this(reses) => enclosing_contract(hir, expr),
248 ExprKind::New(hir::Type { kind: TypeKind::Custom(ItemId::Contract(id)), .. }) => Some(*id),
249 ExprKind::Call(callee, _, _) => {
250 expr_type(hir, expr).and_then(type_contract_id).or_else(|| {
251 match &callee.peel_parens().kind {
252 ExprKind::Ident(reses) => reses.iter().find_map(|res| match res {
253 hir::Res::Item(ItemId::Contract(id)) => Some(*id),
254 _ => None,
255 }),
256 ExprKind::Type(hir::Type {
257 kind: TypeKind::Custom(ItemId::Contract(id)),
258 ..
259 }) => Some(*id),
260 ExprKind::New(hir::Type {
261 kind: TypeKind::Custom(ItemId::Contract(id)),
262 ..
263 }) => Some(*id),
264 _ => None,
265 }
266 })
267 }
268 _ => expr_type(hir, expr).and_then(type_contract_id),
269 }
270}
271
272fn enclosing_contract(hir: &hir::Hir<'_>, expr: &hir::Expr<'_>) -> Option<hir::ContractId> {
274 hir.function_ids()
275 .find_map(|id| {
276 let function = hir.function(id);
277 (function.body.is_some() && function.body_span.contains(expr.span))
278 .then_some(function.contract?)
279 })
280 .or_else(|| hir.contract_ids().find(|&id| hir.contract(id).span.contains(expr.span)))
281}
282
283fn is_this(reses: &[hir::Res]) -> bool {
284 reses.iter().any(|res| matches!(res, hir::Res::Builtin(builtin) if builtin.name() == sym::this))
285}
286
287const fn type_contract_id(ty: &hir::Type<'_>) -> Option<hir::ContractId> {
289 let TypeKind::Custom(ItemId::Contract(id)) = ty.kind else { return None };
290 Some(id)
291}
292fn expr_is_address(hir: &hir::Hir<'_>, expr: &hir::Expr<'_>) -> bool {
294 match &expr.peel_parens().kind {
295 ExprKind::Lit(lit) => matches!(lit.kind, LitKind::Address(_)),
296 ExprKind::Payable(_) => true,
297 ExprKind::Call(callee, _, _) => {
298 matches!(
299 &callee.peel_parens().kind,
300 ExprKind::Type(hir::Type {
301 kind: TypeKind::Elementary(ElementaryType::Address(_)),
302 ..
303 })
304 ) || callee_is_address_returning_builtin(callee)
305 || expr_type(hir, expr).is_some_and(is_address_type)
306 }
307 ExprKind::Member(base, member) if member_is_builtin_address(base, member.name) => true,
308 _ => expr_type(hir, expr).is_some_and(is_address_type),
309 }
310}
311
312fn callee_is_address_returning_builtin(callee: &hir::Expr<'_>) -> bool {
313 let ExprKind::Ident(reses) = &callee.peel_parens().kind else { return false };
314 reses
315 .iter()
316 .any(|res| matches!(res, hir::Res::Builtin(builtin) if builtin.name() == sym::ecrecover))
317}
318
319fn member_is_builtin_address(base: &hir::Expr<'_>, member: Symbol) -> bool {
320 let ExprKind::Ident(reses) = &base.peel_parens().kind else { return false };
321 reses.iter().any(|res| {
322 let hir::Res::Builtin(builtin) = res else { return false };
323 matches!(
324 (builtin.name(), member),
325 (sym::msg, sym::sender) | (sym::block, kw::Coinbase) | (sym::tx, kw::Origin)
326 )
327 })
328}
329
330fn expr_matches_type(hir: &hir::Hir<'_>, expr: &hir::Expr<'_>, ty: &hir::Type<'_>) -> Option<bool> {
332 if let Some(matches) = bool_expr_matches_type(expr, ty) {
333 return Some(matches);
334 }
335
336 if let Some(literal) = integer_literal(expr)
337 && let Some(matches) = integer_literal_matches_type(literal, ty)
338 {
339 return Some(matches);
340 }
341
342 match &expr.peel_parens().kind {
343 ExprKind::Lit(lit) => return Some(lit_matches_type(lit, ty)),
344 ExprKind::Payable(_) => return Some(is_address_type(ty)),
345 _ => {}
346 }
347
348 expr_type(hir, expr).map(|expr_ty| types_match(hir, expr_ty, ty))
349}
350
351fn bool_expr_matches_type(expr: &hir::Expr<'_>, ty: &hir::Type<'_>) -> Option<bool> {
353 expr_is_bool(expr).then(|| is_bool_type(ty))
354}
355
356fn expr_is_bool(expr: &hir::Expr<'_>) -> bool {
357 match &expr.peel_parens().kind {
358 ExprKind::Binary(_, op, _) => matches!(
359 op.kind,
360 hir::BinOpKind::Lt
361 | hir::BinOpKind::Le
362 | hir::BinOpKind::Gt
363 | hir::BinOpKind::Ge
364 | hir::BinOpKind::Eq
365 | hir::BinOpKind::Ne
366 | hir::BinOpKind::Or
367 | hir::BinOpKind::And
368 ),
369 ExprKind::Unary(op, _) => op.kind == hir::UnOpKind::Not,
370 _ => false,
371 }
372}
373
374const fn is_bool_type(ty: &hir::Type<'_>) -> bool {
375 matches!(ty.kind, TypeKind::Elementary(ElementaryType::Bool))
376}
377fn expr_type<'hir>(
379 hir: &'hir hir::Hir<'hir>,
380 expr: &hir::Expr<'hir>,
381) -> Option<&'hir hir::Type<'hir>> {
382 match &expr.peel_parens().kind {
383 ExprKind::Ident(reses) => {
384 let var = reses.iter().filter_map(hir::Res::as_variable).next()?;
385 Some(&hir.variable(var).ty)
386 }
387 ExprKind::Index(base, _) => match &expr_type(hir, base)?.kind {
388 TypeKind::Array(array) => Some(&array.element),
389 TypeKind::Mapping(mapping) => Some(&mapping.value),
390 _ => None,
391 },
392 ExprKind::Member(base, member) => {
393 let TypeKind::Custom(ItemId::Struct(id)) = expr_type(hir, base)?.kind else {
394 return None;
395 };
396 hir.strukt(id).fields.iter().find_map(|&field| {
397 (hir.variable(field).name?.name == member.name).then_some(&hir.variable(field).ty)
398 })
399 }
400 ExprKind::Call(callee, args, _) => match &callee.peel_parens().kind {
401 ExprKind::Type(ty) | ExprKind::New(ty) => Some(ty),
402 _ => call_return_type(hir, callee, args),
403 },
404 ExprKind::Ternary(_, true_, false_) => common_expr_type(hir, true_, false_),
405 _ => None,
406 }
407}
408
409fn common_expr_type<'hir>(
411 hir: &'hir hir::Hir<'hir>,
412 true_: &hir::Expr<'hir>,
413 false_: &hir::Expr<'hir>,
414) -> Option<&'hir hir::Type<'hir>> {
415 let true_ty = expr_type(hir, true_)?;
416 let false_ty = expr_type(hir, false_)?;
417 if types_match(hir, true_ty, false_ty) {
418 Some(false_ty)
419 } else if types_match(hir, false_ty, true_ty) {
420 Some(true_ty)
421 } else {
422 None
423 }
424}
425
426fn call_return_type<'hir>(
428 hir: &'hir hir::Hir<'hir>,
429 callee: &hir::Expr<'hir>,
430 args: &CallArgs<'hir>,
431) -> Option<&'hir hir::Type<'hir>> {
432 let functions = match matching_functions_for_callee(hir, callee, args)? {
433 FunctionCandidates::Exact(functions) | FunctionCandidates::Maybe(functions) => functions,
434 FunctionCandidates::Pointer(returns) => return single_return_type(hir, returns),
435 };
436 let function = single_function(functions.into_iter())?;
437 single_return_type(hir, hir.function(function).returns)
438}
439
440fn single_function(
442 mut functions: impl Iterator<Item = hir::FunctionId>,
443) -> Option<hir::FunctionId> {
444 let function = functions.next()?;
445 functions.next().is_none().then_some(function)
446}
447
448fn single_return_type<'hir>(
450 hir: &'hir hir::Hir<'hir>,
451 returns: &[hir::VariableId],
452) -> Option<&'hir hir::Type<'hir>> {
453 let &[ret] = returns else { return None };
454 Some(&hir.variable(ret).ty)
455}
456
457fn function_type_returns_for_args<'hir>(
459 hir: &hir::Hir<'_>,
460 ty: &'hir hir::Type<'hir>,
461 args: &CallArgs<'_>,
462) -> Option<&'hir [hir::VariableId]> {
463 let TypeKind::Function(function) = ty.kind else { return None };
464 if function.visibility != hir::Visibility::External {
465 return None;
466 }
467 if matches!(params_arg_match(hir, function.parameters, args), ArgMatch::No) {
468 return None;
469 }
470 Some(function.returns)
471}
472
473const fn is_address_type(ty: &hir::Type<'_>) -> bool {
475 matches!(ty.kind, TypeKind::Elementary(ElementaryType::Address(_)))
476}
477
478#[derive(Clone, Copy)]
479struct IntegerLiteral {
480 negative: bool,
481 bits: usize,
482 is_zero: bool,
483 is_power_of_two: bool,
484}
485
486fn integer_literal(expr: &hir::Expr<'_>) -> Option<IntegerLiteral> {
488 match &expr.peel_parens().kind {
489 ExprKind::Lit(lit) => match lit.kind {
490 LitKind::Number(value) => Some(IntegerLiteral {
491 negative: false,
492 bits: value.bit_len().max(1),
493 is_zero: value.is_zero(),
494 is_power_of_two: value.is_power_of_two(),
495 }),
496 _ => None,
497 },
498 ExprKind::Unary(op, expr) if op.kind == hir::UnOpKind::Neg => {
499 let mut literal = integer_literal(expr)?;
500 if !literal.is_zero {
501 literal.negative = !literal.negative;
502 }
503 Some(literal)
504 }
505 _ => None,
506 }
507}
508
509fn integer_literal_matches_type(literal: IntegerLiteral, ty: &hir::Type<'_>) -> Option<bool> {
511 match ty.kind {
512 TypeKind::Elementary(ElementaryType::UInt(size)) => {
513 Some(!literal.negative && literal.bits <= usize::from(size.bits()))
514 }
515 TypeKind::Elementary(ElementaryType::Int(size)) => {
516 let bits = usize::from(size.bits());
517 Some(if literal.negative {
518 literal.bits < bits || (literal.bits == bits && literal.is_power_of_two)
519 } else {
520 literal.bits < bits
521 })
522 }
523 _ => None,
524 }
525}
526
527const fn lit_matches_type(lit: &solar::ast::Lit<'_>, ty: &hir::Type<'_>) -> bool {
529 matches!(
530 (&lit.kind, &ty.kind),
531 (LitKind::Address(_), TypeKind::Elementary(ElementaryType::Address(_)))
532 | (LitKind::Bool(_), TypeKind::Elementary(ElementaryType::Bool))
533 | (
534 LitKind::Number(_),
535 TypeKind::Elementary(ElementaryType::Fixed(_, _) | ElementaryType::UFixed(_, _)),
536 )
537 | (
538 LitKind::Rational(_),
539 TypeKind::Elementary(
540 ElementaryType::Int(_)
541 | ElementaryType::UInt(_)
542 | ElementaryType::Fixed(_, _)
543 | ElementaryType::UFixed(_, _),
544 ),
545 )
546 | (
547 LitKind::Str(StrKind::Str | StrKind::Unicode, ..),
548 TypeKind::Elementary(ElementaryType::String),
549 )
550 | (
551 LitKind::Str(StrKind::Hex, ..),
552 TypeKind::Elementary(ElementaryType::Bytes | ElementaryType::FixedBytes(_)),
553 )
554 )
555}
556
557fn types_match(hir: &hir::Hir<'_>, a: &hir::Type<'_>, b: &hir::Type<'_>) -> bool {
559 match (&a.kind, &b.kind) {
560 (
561 TypeKind::Elementary(ElementaryType::Address(_)),
562 TypeKind::Elementary(ElementaryType::Address(_)),
563 ) => true,
564 (TypeKind::Elementary(a), TypeKind::Elementary(b)) => elementary_type_matches(*a, *b),
565 (TypeKind::Array(a), TypeKind::Array(b)) => {
566 array_sizes_match(hir, a.size, b.size) && types_match(hir, &a.element, &b.element)
567 }
568 (
569 TypeKind::Custom(ItemId::Contract(actual)),
570 TypeKind::Custom(ItemId::Contract(expected)),
571 ) => contract_type_matches(hir, *actual, *expected),
572 (TypeKind::Custom(a), TypeKind::Custom(b)) => a == b,
573 (TypeKind::Function(a), TypeKind::Function(b)) => {
574 a.parameters.len() == b.parameters.len()
575 && a.returns.len() == b.returns.len()
576 && a.parameters
577 .iter()
578 .zip(b.parameters)
579 .all(|(&a, &b)| types_match(hir, &hir.variable(a).ty, &hir.variable(b).ty))
580 && a.returns
581 .iter()
582 .zip(b.returns)
583 .all(|(&a, &b)| types_match(hir, &hir.variable(a).ty, &hir.variable(b).ty))
584 }
585 _ => false,
586 }
587}
588
589fn contract_type_matches(
591 hir: &hir::Hir<'_>,
592 actual: hir::ContractId,
593 expected: hir::ContractId,
594) -> bool {
595 actual == expected || hir.contract(actual).linearized_bases.contains(&expected)
596}
597
598fn array_sizes_match(
600 hir: &hir::Hir<'_>,
601 a: Option<&hir::Expr<'_>>,
602 b: Option<&hir::Expr<'_>>,
603) -> bool {
604 match (a, b) {
605 (None, None) => true,
606 (Some(a), Some(b)) => fixed_array_sizes_match(hir, a, b),
607 _ => false,
608 }
609}
610
611fn fixed_array_sizes_match(hir: &hir::Hir<'_>, a: &hir::Expr<'_>, b: &hir::Expr<'_>) -> bool {
612 matches!(
613 (const_array_size(hir, a), const_array_size(hir, b)),
614 (Some(LitKind::Number(a)), Some(LitKind::Number(b))) if a == b
615 )
616}
617
618type ConstArraySize = LitKind<'static>;
619
620fn const_array_size(hir: &hir::Hir<'_>, expr: &hir::Expr<'_>) -> Option<ConstArraySize> {
621 const MAX_DEPTH: usize = 64;
622 const_array_size_inner(hir, expr, MAX_DEPTH)
623}
624
625fn const_array_size_inner(
626 hir: &hir::Hir<'_>,
627 expr: &hir::Expr<'_>,
628 depth: usize,
629) -> Option<ConstArraySize> {
630 if depth == 0 {
631 return None;
632 }
633
634 match &expr.peel_parens().kind {
635 ExprKind::Ident(reses) => {
636 let var = reses.iter().find_map(hir::Res::as_variable)?;
637 let var = hir.variable(var);
638 if !var.is_constant() {
639 return None;
640 }
641 const_array_size_inner(hir, var.initializer?, depth - 1)
642 }
643 ExprKind::Lit(lit) => match lit.kind {
644 LitKind::Number(value) => Some(LitKind::Number(value)),
645 _ => None,
646 },
647 ExprKind::Unary(op, expr) => {
648 let LitKind::Number(value) = const_array_size_inner(hir, expr, depth - 1)? else {
649 return None;
650 };
651 match op.kind {
652 hir::UnOpKind::BitNot => Some(LitKind::Number(!value)),
653 hir::UnOpKind::Neg => Some(LitKind::Number(value.wrapping_neg())),
654 _ => None,
655 }
656 }
657 ExprKind::Binary(left, op, right) => {
658 let left = const_array_size_inner(hir, left, depth - 1)?;
659 let right = const_array_size_inner(hir, right, depth - 1)?;
660 const_array_size_binary_op(left, op.kind, right)
661 }
662 _ => None,
663 }
664}
665
666fn const_array_size_binary_op(
667 left: ConstArraySize,
668 op: hir::BinOpKind,
669 right: ConstArraySize,
670) -> Option<ConstArraySize> {
671 let (LitKind::Number(left), LitKind::Number(right)) = (left, right) else {
672 return None;
673 };
674
675 let value = match op {
676 hir::BinOpKind::BitOr => left | right,
677 hir::BinOpKind::BitAnd => left & right,
678 hir::BinOpKind::BitXor => left ^ right,
679 hir::BinOpKind::Shr => left.wrapping_shr(right.try_into().unwrap_or(usize::MAX)),
680 hir::BinOpKind::Shl => left.wrapping_shl(right.try_into().unwrap_or(usize::MAX)),
681 hir::BinOpKind::Sar => left.arithmetic_shr(right.try_into().unwrap_or(usize::MAX)),
682 hir::BinOpKind::Add => left.checked_add(right)?,
683 hir::BinOpKind::Sub => left.checked_sub(right)?,
684 hir::BinOpKind::Mul => left.checked_mul(right)?,
685 hir::BinOpKind::Div => left.checked_div(right)?,
686 hir::BinOpKind::Rem => left.checked_rem(right)?,
687 hir::BinOpKind::Pow => left.checked_pow(right)?,
688 hir::BinOpKind::Lt
689 | hir::BinOpKind::Le
690 | hir::BinOpKind::Gt
691 | hir::BinOpKind::Ge
692 | hir::BinOpKind::Eq
693 | hir::BinOpKind::Ne
694 | hir::BinOpKind::Or
695 | hir::BinOpKind::And => return None,
696 };
697 Some(LitKind::Number(value))
698}
699
700fn elementary_type_matches(actual: ElementaryType, expected: ElementaryType) -> bool {
702 use ElementaryType::{Address, Bool, Bytes, Fixed, FixedBytes, Int, String, UFixed, UInt};
703
704 match (actual, expected) {
705 (Address(_), Address(false)) => true,
706 (Address(payable), Address(true)) => payable,
707 (UInt(actual), UInt(expected))
708 | (Int(actual), Int(expected))
709 | (FixedBytes(actual), FixedBytes(expected)) => actual.bits() <= expected.bits(),
710 (Bool, Bool) | (String, String) | (Bytes, Bytes) => true,
711 (Fixed(actual_size, actual_scale), Fixed(expected_size, expected_scale))
712 | (UFixed(actual_size, actual_scale), UFixed(expected_size, expected_scale)) => {
713 actual_size == expected_size && actual_scale == expected_scale
714 }
715 _ => false,
716 }
717}
718
719fn is_dynamic_type(hir: &hir::Hir<'_>, ty: &hir::Type<'_>) -> bool {
721 match &ty.kind {
722 TypeKind::Elementary(ElementaryType::Bytes | ElementaryType::String) => true,
723 TypeKind::Array(array) => array.size.is_none() || is_dynamic_type(hir, &array.element),
724 TypeKind::Custom(ItemId::Struct(id)) => hir
725 .strukt(*id)
726 .fields
727 .iter()
728 .any(|&field| is_dynamic_type(hir, &hir.variable(field).ty)),
729 _ => false,
730 }
731}