@@ -2,6 +2,7 @@ use crate::core::stack::StackFrame;
22
33/// Check if a condition is tautologically true (e.g., "arg0 == arg0", "X == (address(X))").
44/// These create infinite loops and should be skipped as invalid loop conditions.
5+ /// Also handles bitmask patterns like "X == (X & 0xff...ff)" which are type-check equivalents.
56pub ( crate ) fn is_tautologically_true_condition ( condition : & str ) -> bool {
67 let mut trimmed = condition. trim ( ) ;
78
@@ -53,8 +54,9 @@ pub(crate) fn is_tautologically_true_condition(condition: &str) -> bool {
5354 false
5455}
5556
56- /// Normalize an expression for comparison by stripping type casts and parentheses.
57+ /// Normalize an expression for comparison by stripping type casts, bitmasks, and parentheses.
5758/// For example: "address(arg0)" -> "arg0", "(arg0)" -> "arg0"
59+ /// Also handles bitmask patterns like "(arg0) & (0xff...ff)" which are equivalent to type casts.
5860fn normalize_for_comparison ( expr : & str ) -> String {
5961 let mut result = expr. trim ( ) . to_string ( ) ;
6062
@@ -101,9 +103,53 @@ fn normalize_for_comparison(expr: &str) -> String {
101103 }
102104 }
103105
106+ // Strip bitmask patterns like "(X) & (0xff...ff)" which are type-cast equivalents
107+ // These are used by Solidity to ensure values fit in certain types
108+ // Common masks:
109+ // - 0xffffffffffffffffffffffffffffffffffffffff (160 bits = address)
110+ // - 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff (256 bits)
111+ // - 0xff (8 bits), 0xffff (16 bits), etc.
112+ if let Some ( and_pos) = result. find ( " & " ) {
113+ let lhs = result[ ..and_pos] . trim ( ) ;
114+ let rhs = result[ and_pos + 3 ..] . trim ( ) ;
115+
116+ // Check if rhs is a bitmask (all f's hex value)
117+ if is_bitmask ( rhs) {
118+ // Return the normalized lhs (the actual value being masked)
119+ return normalize_for_comparison ( lhs) ;
120+ }
121+ // Also check if lhs is the mask and rhs is the value
122+ if is_bitmask ( lhs) {
123+ return normalize_for_comparison ( rhs) ;
124+ }
125+ }
126+
104127 result
105128}
106129
130+ /// Check if a string represents a bitmask (0xff...ff pattern)
131+ fn is_bitmask ( s : & str ) -> bool {
132+ let mut trimmed = s. trim ( ) ;
133+
134+ // Strip parentheses
135+ while trimmed. starts_with ( '(' ) && trimmed. ends_with ( ')' ) {
136+ let inner = & trimmed[ 1 ..trimmed. len ( ) - 1 ] ;
137+ if is_balanced_parens ( inner) {
138+ trimmed = inner. trim ( ) ;
139+ } else {
140+ break ;
141+ }
142+ }
143+
144+ // Check for hex pattern 0xff...ff
145+ if let Some ( hex_str) = trimmed. strip_prefix ( "0x" ) {
146+ // Must be non-empty and all 'f' characters (case insensitive)
147+ !hex_str. is_empty ( ) && hex_str. chars ( ) . all ( |c| c == 'f' || c == 'F' )
148+ } else {
149+ false
150+ }
151+ }
152+
107153/// Check if parentheses are balanced in a string
108154fn is_balanced_parens ( s : & str ) -> bool {
109155 let mut depth = 0 ;
@@ -846,6 +892,48 @@ mod tests {
846892
847893 // Nested type casts
848894 assert_eq ! ( normalize_for_comparison( "address(uint256(arg0))" ) , "arg0" ) ;
895+
896+ // Bitmask patterns (equivalent to type casts)
897+ assert_eq ! (
898+ normalize_for_comparison( "(arg1) & (0xffffffffffffffffffffffffffffffffffffffff)" ) ,
899+ "arg1"
900+ ) ;
901+ assert_eq ! (
902+ normalize_for_comparison( "((arg1) & (0xffffffffffffffffffffffffffffffffffffffff))" ) ,
903+ "arg1"
904+ ) ;
905+ assert_eq ! ( normalize_for_comparison( "arg0 & 0xff" ) , "arg0" ) ;
906+ assert_eq ! ( normalize_for_comparison( "(arg0) & (0xffff)" ) , "arg0" ) ;
907+ }
908+
909+ #[ test]
910+ fn test_is_bitmask ( ) {
911+ // Valid bitmasks
912+ assert ! ( is_bitmask( "0xff" ) ) ;
913+ assert ! ( is_bitmask( "0xffff" ) ) ;
914+ assert ! ( is_bitmask( "0xffffffffffffffffffffffffffffffffffffffff" ) ) ;
915+ assert ! ( is_bitmask( "(0xff)" ) ) ;
916+ assert ! ( is_bitmask( "((0xffff))" ) ) ;
917+ assert ! ( is_bitmask( "0xFFFF" ) ) ; // uppercase
918+
919+ // Invalid bitmasks
920+ assert ! ( !is_bitmask( "0x01" ) ) ;
921+ assert ! ( !is_bitmask( "0xfe" ) ) ;
922+ assert ! ( !is_bitmask( "arg0" ) ) ;
923+ assert ! ( !is_bitmask( "0x" ) ) ;
924+ assert ! ( !is_bitmask( "" ) ) ;
925+ }
926+
927+ #[ test]
928+ fn test_is_tautologically_true_with_bitmask ( ) {
929+ // Bitmask patterns that are tautologically true
930+ assert ! ( is_tautologically_true_condition(
931+ "arg1 == ((arg1) & (0xffffffffffffffffffffffffffffffffffffffff))"
932+ ) ) ;
933+ assert ! ( is_tautologically_true_condition( "arg0 == (arg0 & 0xff)" ) ) ;
934+ assert ! ( is_tautologically_true_condition(
935+ "((arg1) & (0xffffffffffffffffffffffffffffffffffffffff)) == arg1"
936+ ) ) ;
849937 }
850938
851939 #[ test]
0 commit comments