Skip to content

Commit 7ab3520

Browse files
fix(js_analyze): update noPrecisionLoss to correctly handle large hex literals
1 parent 7411911 commit 7ab3520

File tree

5 files changed

+153
-18
lines changed

5 files changed

+153
-18
lines changed

crates/biome_js_analyze/src/lint/correctness/no_precision_loss.rs

Lines changed: 55 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,3 @@
1-
use std::num::IntErrorKind;
2-
use std::ops::RangeInclusive;
3-
41
use biome_analyze::context::RuleContext;
52
use biome_analyze::{Ast, Rule, RuleDiagnostic, RuleSource, declare_lint_rule};
63
use biome_console::markup;
@@ -120,21 +117,64 @@ fn is_precision_lost_in_base_10(num: &str) -> Option<bool> {
120117
}
121118

122119
fn is_precision_lost_in_base_other(num: &str, radix: u8) -> bool {
123-
let parsed = match i64::from_str_radix(num, radix as u32) {
124-
Ok(x) => x,
125-
Err(e) => {
126-
return matches!(
127-
e.kind(),
128-
IntErrorKind::PosOverflow | IntErrorKind::NegOverflow
129-
);
120+
let mut msb: Option<u64> = None;
121+
let mut lsb: Option<u64> = None;
122+
let mut current_bit_index: u64 = 0;
123+
124+
// Iterate over digits in reverse order (least significant first)
125+
for c in num.chars().rev() {
126+
if c == '_' {
127+
continue;
130128
}
131-
};
129+
let digit = match c.to_digit(radix as u32) {
130+
Some(d) => d,
131+
None => return false, // Should not happen for valid literals
132+
};
133+
134+
// Check bits of the digit
135+
let bits_per_digit = match radix {
136+
16 => 4,
137+
8 => 3,
138+
2 => 1,
139+
_ => unreachable!("radix must be 2, 8, or 16"),
140+
};
141+
142+
for i in 0..bits_per_digit {
143+
if (digit >> i) & 1 == 1 {
144+
let bit_pos = current_bit_index + i;
145+
if lsb.is_none() {
146+
lsb = Some(bit_pos);
147+
}
148+
msb = Some(bit_pos);
149+
}
150+
}
151+
current_bit_index += bits_per_digit;
152+
}
132153

133-
const MAX_SAFE_INTEGER: i64 = 2_i64.pow(53) - 1;
134-
const MIN_SAFE_INTEGER: i64 = -MAX_SAFE_INTEGER;
135-
const SAFE_RANGE: RangeInclusive<i64> = MIN_SAFE_INTEGER..=MAX_SAFE_INTEGER;
154+
if let (Some(msb), Some(lsb)) = (msb, lsb) {
155+
// Check for overflow (exponent > 1023)
156+
// In IEEE 754 double precision:
157+
// - The exponent bias is 1023.
158+
// - The maximum valid exponent is 1023 (representing 2^1023).
159+
// - 2^1024 overflows to Infinity.
160+
// Thus, if the most significant bit is at index 1024 or greater, the number overflows.
161+
if msb >= 1024 {
162+
return true;
163+
}
164+
165+
// Check for precision loss
166+
// In IEEE 754 double precision:
167+
// - The significand (mantissa) has 53 bits of precision (52 stored bits + 1 implicit leading bit).
168+
// - If the distance between the most significant bit (MSB) and the least significant bit (LSB)
169+
// exceeds 53 bits, the number cannot be exactly represented, as the lower bits would be truncated.
170+
// Span = MSB - LSB + 1
171+
let span = msb - lsb + 1;
136172

137-
!SAFE_RANGE.contains(&parsed)
173+
span > 53
174+
} else {
175+
// Value is 0
176+
false
177+
}
138178
}
139179

140180
fn remove_leading_zeros(num: &str) -> &str {

crates/biome_js_analyze/tests/specs/correctness/noPrecisionLoss/invalid.js

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,4 +33,10 @@ var x = 0B10000000000_0000000000000000000000000000_000000000000001
3333
var x = 0o4_00000000000000_001
3434
var x = 0O4_0000000000000000_1
3535
var x = 0x2_0000000000001
36-
var x = 0X200000_0000000_1
36+
var x = 0X200000_0000000_1
37+
// From repro_issue.js
38+
var x = 0x20000000000001; // 2^53 + 1 (Invalid, precision loss)
39+
var x = 9007199254740993; // 2^53 + 1 (Invalid, precision loss)
40+
var x = 0x10000000000000000000000001; // 2^100 + 1 (Invalid, precision loss)
41+
// 2^1024 (Overflow, Invalid)
42+
var x = 0x10000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000;

crates/biome_js_analyze/tests/specs/correctness/noPrecisionLoss/invalid.js.snap

Lines changed: 77 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
---
22
source: crates/biome_js_analyze/tests/spec_tests.rs
33
expression: invalid.js
4-
snapshot_kind: text
54
---
65
# Input
76
```js
@@ -41,6 +40,13 @@ var x = 0o4_00000000000000_001
4140
var x = 0O4_0000000000000000_1
4241
var x = 0x2_0000000000001
4342
var x = 0X200000_0000000_1
43+
// From repro_issue.js
44+
var x = 0x20000000000001; // 2^53 + 1 (Invalid, precision loss)
45+
var x = 9007199254740993; // 2^53 + 1 (Invalid, precision loss)
46+
var x = 0x10000000000000000000000001; // 2^100 + 1 (Invalid, precision loss)
47+
// 2^1024 (Overflow, Invalid)
48+
var x = 0x10000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000;
49+
4450
```
4551

4652
# Diagnostics
@@ -614,6 +620,7 @@ invalid.js:35:9 lint/correctness/noPrecisionLoss ━━━━━━━━━━
614620
> 35 │ var x = 0x2_0000000000001
615621
│ ^^^^^^^^^^^^^^^^^
616622
36 │ var x = 0X200000_0000000_1
623+
37 │ // From repro_issue.js
617624
618625
i The value at runtime will be 9007199254740992
619626
@@ -629,8 +636,77 @@ invalid.js:36:9 lint/correctness/noPrecisionLoss ━━━━━━━━━━
629636
35 │ var x = 0x2_0000000000001
630637
> 36 │ var x = 0X200000_0000000_1
631638
│ ^^^^^^^^^^^^^^^^^^
639+
37 │ // From repro_issue.js
640+
38 │ var x = 0x20000000000001; // 2^53 + 1 (Invalid, precision loss)
641+
642+
i The value at runtime will be 9007199254740992
643+
644+
645+
```
646+
647+
```
648+
invalid.js:38:9 lint/correctness/noPrecisionLoss ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
649+
650+
× This number literal will lose precision at runtime.
651+
652+
36 │ var x = 0X200000_0000000_1
653+
37 │ // From repro_issue.js
654+
> 38 │ var x = 0x20000000000001; // 2^53 + 1 (Invalid, precision loss)
655+
│ ^^^^^^^^^^^^^^^^
656+
39 │ var x = 9007199254740993; // 2^53 + 1 (Invalid, precision loss)
657+
40 │ var x = 0x10000000000000000000000001; // 2^100 + 1 (Invalid, precision loss)
632658
633659
i The value at runtime will be 9007199254740992
634660
635661
636662
```
663+
664+
```
665+
invalid.js:39:9 lint/correctness/noPrecisionLoss ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
666+
667+
× This number literal will lose precision at runtime.
668+
669+
37 │ // From repro_issue.js
670+
38 │ var x = 0x20000000000001; // 2^53 + 1 (Invalid, precision loss)
671+
> 39 │ var x = 9007199254740993; // 2^53 + 1 (Invalid, precision loss)
672+
│ ^^^^^^^^^^^^^^^^
673+
40 │ var x = 0x10000000000000000000000001; // 2^100 + 1 (Invalid, precision loss)
674+
41 │ // 2^1024 (Overflow, Invalid)
675+
676+
i The value at runtime will be 9007199254740992
677+
678+
679+
```
680+
681+
```
682+
invalid.js:40:9 lint/correctness/noPrecisionLoss ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
683+
684+
× This number literal will lose precision at runtime.
685+
686+
38 │ var x = 0x20000000000001; // 2^53 + 1 (Invalid, precision loss)
687+
39 │ var x = 9007199254740993; // 2^53 + 1 (Invalid, precision loss)
688+
> 40 │ var x = 0x10000000000000000000000001; // 2^100 + 1 (Invalid, precision loss)
689+
│ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
690+
41 │ // 2^1024 (Overflow, Invalid)
691+
42 │ var x = 0x10000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000;
692+
693+
i The value at runtime will be 1267650600228229400000000000000
694+
695+
696+
```
697+
698+
```
699+
invalid.js:42:9 lint/correctness/noPrecisionLoss ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
700+
701+
× This number literal will lose precision at runtime.
702+
703+
40 │ var x = 0x10000000000000000000000001; // 2^100 + 1 (Invalid, precision loss)
704+
41 │ // 2^1024 (Overflow, Invalid)
705+
> 42 │ var x = 0x10000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000;
706+
│ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
707+
43 │
708+
709+
i The value at runtime will be inf
710+
711+
712+
```

crates/biome_js_analyze/tests/specs/correctness/noPrecisionLoss/valid.js

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,4 +76,10 @@ var x = 0X1_FFF_FFFF_FFF_FFF
7676
123_00000000000000000000_00.0_0;
7777

7878
var MAX = 1.7976931348623157e+308;
79-
var MIN = 5e-324;
79+
var MIN = 5e-324;
80+
// From repro_issue.js
81+
var x = 0x20000000000000; // 2^53 (Valid)
82+
var x = 9007199254740992; // 2^53 (Valid)
83+
var x = 0x10000000000000000000000000; // 2^100 (Valid)
84+
// 2^1023 (Max exponent, Valid)
85+
var x = 0x8000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000;

crates/biome_js_analyze/tests/specs/correctness/noPrecisionLoss/valid.js.snap

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,4 +83,11 @@ var x = 0X1_FFF_FFFF_FFF_FFF
8383
8484
var MAX = 1.7976931348623157e+308;
8585
var MIN = 5e-324;
86+
// From repro_issue.js
87+
var x = 0x20000000000000; // 2^53 (Valid)
88+
var x = 9007199254740992; // 2^53 (Valid)
89+
var x = 0x10000000000000000000000000; // 2^100 (Valid)
90+
// 2^1023 (Max exponent, Valid)
91+
var x = 0x8000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000;
92+
8693
```

0 commit comments

Comments
 (0)