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::hir::{self, ElementaryType, ExprKind, ItemId, Res, TypeKind},
10};
11
12declare_forge_lint!(
13 TYPE_BASED_TAUTOLOGY,
14 Severity::Med,
15 "type-based-tautology",
16 "condition is always true or false based on the variable's type"
17);
18
19impl<'hir> LateLintPass<'hir> for TypeBasedTautology {
20 fn check_expr(
21 &mut self,
22 ctx: &LintContext,
23 hir: &'hir hir::Hir<'hir>,
24 expr: &'hir hir::Expr<'hir>,
25 ) {
26 let ExprKind::Binary(left, op, right) = &expr.kind else { return };
27
28 if !matches!(
30 op.kind,
31 BinOpKind::Lt
32 | BinOpKind::Le
33 | BinOpKind::Gt
34 | BinOpKind::Ge
35 | BinOpKind::Eq
36 | BinOpKind::Ne
37 ) {
38 return;
39 }
40
41 if let Some(elem_ty) = elem_type_of(hir, left)
43 && let Some((val_neg, val_mag)) = lit_value_of(right)
44 && is_tautology(elem_ty, val_neg, val_mag, op.kind)
45 {
46 ctx.emit(&TYPE_BASED_TAUTOLOGY, expr.span);
47 return;
48 }
49
50 if let Some((val_neg, val_mag)) = lit_value_of(left)
52 && let Some(elem_ty) = elem_type_of(hir, right)
53 && is_tautology(elem_ty, val_neg, val_mag, flip(op.kind))
54 {
55 ctx.emit(&TYPE_BASED_TAUTOLOGY, expr.span);
56 }
57 }
58}
59
60const fn flip(op: BinOpKind) -> BinOpKind {
63 match op {
64 BinOpKind::Lt => BinOpKind::Gt,
65 BinOpKind::Le => BinOpKind::Ge,
66 BinOpKind::Gt => BinOpKind::Lt,
67 BinOpKind::Ge => BinOpKind::Le,
68 BinOpKind::Eq | BinOpKind::Ne => op, _ => unreachable!(),
70 }
71}
72
73fn is_tautology(ty: ElementaryType, val_neg: bool, val_mag: U256, op: BinOpKind) -> bool {
79 match ty {
80 ElementaryType::UInt(size) => {
81 let bits = size.bits();
83 let hi =
84 if bits == 256 { U256::MAX } else { (U256::from(1u8) << bits) - U256::from(1u8) };
85 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 {
90 BinOpKind::Gt | BinOpKind::Le => hi_le_val || val_lt_lo,
91 BinOpKind::Ge | BinOpKind::Lt => val_le_lo || hi_lt_val,
92 BinOpKind::Eq | BinOpKind::Ne => hi_lt_val || val_lt_lo,
93 _ => false,
94 }
95 }
96 ElementaryType::Int(size) => {
97 let bits = size.bits();
99 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 {
106 BinOpKind::Gt | BinOpKind::Le => hi_le_val || val_lt_lo,
107 BinOpKind::Ge | BinOpKind::Lt => val_le_lo || hi_lt_val,
108 BinOpKind::Eq | BinOpKind::Ne => hi_lt_val || val_lt_lo,
109 _ => false,
110 }
111 }
112 _ => false,
113 }
114}
115
116fn elem_type_of<'hir>(
118 hir: &'hir hir::Hir<'hir>,
119 expr: &'hir hir::Expr<'hir>,
120) -> Option<ElementaryType> {
121 match &expr.peel_parens().kind {
122 ExprKind::Ident(resolutions) => {
123 if let Some(Res::Item(ItemId::Variable(var_id))) = resolutions.first()
124 && let TypeKind::Elementary(ty) = hir.variable(*var_id).ty.kind
125 {
126 return Some(ty);
127 }
128 None
129 }
130 ExprKind::Call(call_expr, _, _) => {
132 if let ExprKind::Type(hir::Type { kind: TypeKind::Elementary(ty), .. }) =
133 &call_expr.kind
134 {
135 return Some(*ty);
136 }
137 None
138 }
139 _ => None,
140 }
141}
142
143fn lit_value_of(expr: &hir::Expr<'_>) -> Option<(bool, U256)> {
146 match &expr.peel_parens().kind {
147 ExprKind::Lit(lit) => {
148 if let LitKind::Number(n) = lit.kind {
149 return Some((false, n));
150 }
151 None
152 }
153 ExprKind::Unary(op, inner) if op.kind == UnOpKind::Neg => {
154 if let ExprKind::Lit(lit) = &inner.peel_parens().kind
155 && let LitKind::Number(n) = lit.kind
156 {
157 return Some((true, n));
158 }
159 None
160 }
161 _ => None,
162 }
163}