forge_lint/sol/med/
tautology.rs1use super::TypeBasedTautology;
2use crate::{
3 linter::{LateLintPass, LintContext},
4 sol::{Severity, SolLint},
5};
6use alloy_primitives::U256;
7use solar::{
8 ast::{BinOpKind, LitKind, UnOpKind},
9 sema::{
10 Gcx,
11 hir::{self, ElementaryType, ExprKind, ItemId, Res, TypeKind},
12 },
13};
14
15declare_forge_lint!(
16 TYPE_BASED_TAUTOLOGY,
17 Severity::Med,
18 "type-based-tautology",
19 "condition is always true or false based on the variable's type"
20);
21
22impl<'hir> LateLintPass<'hir> for TypeBasedTautology {
23 fn check_expr(
24 &mut self,
25 ctx: &LintContext,
26 _gcx: Gcx<'hir>,
27 hir: &'hir hir::Hir<'hir>,
28 expr: &'hir hir::Expr<'hir>,
29 ) {
30 let ExprKind::Binary(left, op, right) = &expr.kind else { return };
31
32 if !matches!(
34 op.kind,
35 BinOpKind::Lt
36 | BinOpKind::Le
37 | BinOpKind::Gt
38 | BinOpKind::Ge
39 | BinOpKind::Eq
40 | BinOpKind::Ne
41 ) {
42 return;
43 }
44
45 if let Some(elem_ty) = elem_type_of(hir, left)
47 && let Some((val_neg, val_mag)) = lit_value_of(right)
48 && is_tautology(elem_ty, val_neg, val_mag, op.kind)
49 {
50 ctx.emit(&TYPE_BASED_TAUTOLOGY, expr.span);
51 return;
52 }
53
54 if let Some((val_neg, val_mag)) = lit_value_of(left)
56 && let Some(elem_ty) = elem_type_of(hir, right)
57 && is_tautology(elem_ty, val_neg, val_mag, flip(op.kind))
58 {
59 ctx.emit(&TYPE_BASED_TAUTOLOGY, expr.span);
60 }
61 }
62}
63
64const fn flip(op: BinOpKind) -> BinOpKind {
67 match op {
68 BinOpKind::Lt => BinOpKind::Gt,
69 BinOpKind::Le => BinOpKind::Ge,
70 BinOpKind::Gt => BinOpKind::Lt,
71 BinOpKind::Ge => BinOpKind::Le,
72 BinOpKind::Eq | BinOpKind::Ne => op, _ => unreachable!(),
74 }
75}
76
77fn is_tautology(ty: ElementaryType, val_neg: bool, val_mag: U256, op: BinOpKind) -> bool {
83 match ty {
84 ElementaryType::UInt(size) => {
85 let bits = size.bits();
87 let hi =
88 if bits == 256 { U256::MAX } else { (U256::from(1u8) << bits) - U256::from(1u8) };
89 let val_lt_lo = val_neg && val_mag != U256::ZERO; let val_le_lo = val_neg || val_mag == U256::ZERO; let hi_lt_val = !val_neg && val_mag > hi; let hi_le_val = !val_neg && val_mag >= hi; match op {
94 BinOpKind::Gt | BinOpKind::Le => hi_le_val || val_lt_lo,
95 BinOpKind::Ge | BinOpKind::Lt => val_le_lo || hi_lt_val,
96 BinOpKind::Eq | BinOpKind::Ne => hi_lt_val || val_lt_lo,
97 _ => false,
98 }
99 }
100 ElementaryType::Int(size) => {
101 let bits = size.bits();
103 let half = U256::from(1u8) << (bits - 1); let hi = half - U256::from(1u8); let val_lt_lo = val_neg && val_mag > half; let val_le_lo = val_neg && val_mag >= half; let hi_lt_val = !val_neg && val_mag > hi; let hi_le_val = !val_neg && val_mag >= hi; match op {
110 BinOpKind::Gt | BinOpKind::Le => hi_le_val || val_lt_lo,
111 BinOpKind::Ge | BinOpKind::Lt => val_le_lo || hi_lt_val,
112 BinOpKind::Eq | BinOpKind::Ne => hi_lt_val || val_lt_lo,
113 _ => false,
114 }
115 }
116 _ => false,
117 }
118}
119
120fn elem_type_of<'hir>(
122 hir: &'hir hir::Hir<'hir>,
123 expr: &'hir hir::Expr<'hir>,
124) -> Option<ElementaryType> {
125 match &expr.peel_parens().kind {
126 ExprKind::Ident(resolutions) => {
127 if let Some(Res::Item(ItemId::Variable(var_id))) = resolutions.first()
128 && let TypeKind::Elementary(ty) = hir.variable(*var_id).ty.kind
129 {
130 return Some(ty);
131 }
132 None
133 }
134 ExprKind::Call(call_expr, _, _) => {
136 if let ExprKind::Type(hir::Type { kind: TypeKind::Elementary(ty), .. }) =
137 &call_expr.kind
138 {
139 return Some(*ty);
140 }
141 None
142 }
143 _ => None,
144 }
145}
146
147fn lit_value_of(expr: &hir::Expr<'_>) -> Option<(bool, U256)> {
150 match &expr.peel_parens().kind {
151 ExprKind::Lit(lit) => {
152 if let LitKind::Number(n) = lit.kind {
153 return Some((false, n));
154 }
155 None
156 }
157 ExprKind::Unary(op, inner) if op.kind == UnOpKind::Neg => {
158 if let ExprKind::Lit(lit) = &inner.peel_parens().kind
159 && let LitKind::Number(n) = lit.kind
160 {
161 return Some((true, n));
162 }
163 None
164 }
165 _ => None,
166 }
167}