Skip to content

Commit bc24d80

Browse files
authored
fix: When coercing scalars make sure values don't exceed maximum value (#4381)
Signed-off-by: Robert Kruszewski <[email protected]>
1 parent 50c3d57 commit bc24d80

File tree

3 files changed

+88
-12
lines changed

3 files changed

+88
-12
lines changed

vortex-scalar/src/list.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -184,7 +184,7 @@ impl<'a> ListScalar<'a> {
184184
// Recursively cast the elements of the list.
185185
Scalar::new(DType::clone(self.element_dtype), element.clone())
186186
.cast(target_element_dtype)
187-
.map(|x| x.value().clone())
187+
.map(|x| x.into_value())
188188
})
189189
.collect::<VortexResult<Arc<[ScalarValue]>>>()?,
190190
)),

vortex-scalar/src/pvalue.rs

Lines changed: 86 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,9 @@ use num_traits::NumCast;
99
use paste::paste;
1010
use vortex_dtype::half::f16;
1111
use vortex_dtype::{NativePType, PType, ToBytes};
12-
use vortex_error::{VortexError, VortexExpect, VortexResult, vortex_bail, vortex_err};
12+
use vortex_error::{
13+
VortexError, VortexExpect, VortexResult, vortex_bail, vortex_ensure, vortex_err,
14+
};
1315

1416
/// A primitive value that can represent any primitive type supported by Vortex.
1517
///
@@ -444,8 +446,20 @@ impl CoercePValue for f16 {
444446
match value {
445447
PValue::U8(u) => Ok(Self::from_bits(u as u16)),
446448
PValue::U16(u) => Ok(Self::from_bits(u)),
447-
PValue::U32(u) => Ok(Self::from_bits(u as u16)),
448-
PValue::U64(u) => Ok(Self::from_bits(u as u16)),
449+
PValue::U32(u) => {
450+
vortex_ensure!(
451+
u <= u16::MAX as u32,
452+
"Cannot coerce U32 value to f16: value out of range"
453+
);
454+
Ok(Self::from_bits(u as u16))
455+
}
456+
PValue::U64(u) => {
457+
vortex_ensure!(
458+
u <= u16::MAX as u64,
459+
"Cannot coerce U64 value to f16: value out of range"
460+
);
461+
Ok(Self::from_bits(u as u16))
462+
}
449463
PValue::F16(u) => Ok(u),
450464
PValue::F32(f) => {
451465
<Self as NumCast>::from(f).ok_or_else(|| vortex_err!("Cannot convert f32 to f16"))
@@ -468,7 +482,13 @@ impl CoercePValue for f32 {
468482
PValue::U8(u) => Ok(Self::from_bits(u as u32)),
469483
PValue::U16(u) => Ok(Self::from_bits(u as u32)),
470484
PValue::U32(u) => Ok(Self::from_bits(u)),
471-
PValue::U64(u) => Ok(Self::from_bits(u as u32)),
485+
PValue::U64(u) => {
486+
vortex_ensure!(
487+
u <= u32::MAX as u64,
488+
"Cannot coerce U64 value to f32: value out of range"
489+
);
490+
Ok(Self::from_bits(u as u32))
491+
}
472492
PValue::F16(f) => {
473493
<Self as NumCast>::from(f).ok_or_else(|| vortex_err!("Cannot convert f16 to f32"))
474494
}
@@ -511,10 +531,11 @@ mod test {
511531
use std::cmp::Ordering;
512532
use std::collections::HashSet;
513533

514-
use vortex_dtype::PType;
515534
use vortex_dtype::half::f16;
535+
use vortex_dtype::{PType, ToBytes};
516536

517537
use crate::PValue;
538+
use crate::pvalue::CoercePValue;
518539

519540
#[test]
520541
pub fn test_is_instance_of() {
@@ -781,8 +802,6 @@ mod test {
781802

782803
#[test]
783804
fn test_to_le_bytes() {
784-
use vortex_dtype::ToBytes;
785-
786805
assert_eq!(PValue::U8(0x12).to_le_bytes(), &[0x12]);
787806
assert_eq!(PValue::U16(0x1234).to_le_bytes(), &[0x34, 0x12]);
788807
assert_eq!(
@@ -821,8 +840,6 @@ mod test {
821840

822841
#[test]
823842
fn test_coerce_pvalue() {
824-
use super::CoercePValue;
825-
826843
// Test integer coercion
827844
assert_eq!(u32::coerce(PValue::U16(42)).unwrap(), 42u32);
828845
assert_eq!(i64::coerce(PValue::I32(-42)).unwrap(), -42i64);
@@ -834,4 +851,64 @@ mod test {
834851
1.0f64
835852
);
836853
}
854+
855+
#[test]
856+
fn test_coerce_f16_beyond_u16_max() {
857+
// Test U32 to f16 coercion within valid range
858+
assert!(f16::coerce(PValue::U32(u16::MAX as u32)).is_ok());
859+
assert_eq!(
860+
f16::coerce(PValue::U32(0x3C00)).unwrap(),
861+
f16::from_bits(0x3C00) // 1.0 in f16
862+
);
863+
864+
// Test U32 to f16 coercion beyond u16::MAX - should fail
865+
assert!(f16::coerce(PValue::U32((u16::MAX as u32) + 1)).is_err());
866+
assert!(f16::coerce(PValue::U32(u32::MAX)).is_err());
867+
868+
// Test U64 to f16 coercion within valid range
869+
assert!(f16::coerce(PValue::U64(u16::MAX as u64)).is_ok());
870+
assert_eq!(
871+
f16::coerce(PValue::U64(0x3C00)).unwrap(),
872+
f16::from_bits(0x3C00) // 1.0 in f16
873+
);
874+
875+
// Test U64 to f16 coercion beyond u16::MAX - should fail
876+
assert!(f16::coerce(PValue::U64((u16::MAX as u64) + 1)).is_err());
877+
assert!(f16::coerce(PValue::U64(u32::MAX as u64)).is_err());
878+
assert!(f16::coerce(PValue::U64(u64::MAX)).is_err());
879+
}
880+
881+
#[test]
882+
fn test_coerce_f32_beyond_u32_max() {
883+
// Test U64 to f32 coercion within valid range
884+
assert!(f32::coerce(PValue::U64(u32::MAX as u64)).is_ok());
885+
assert_eq!(
886+
f32::coerce(PValue::U64(0x3f800000)).unwrap(),
887+
1.0f32 // 0x3f800000 is 1.0 in f32
888+
);
889+
890+
// Test U64 to f32 coercion beyond u32::MAX - should fail
891+
assert!(f32::coerce(PValue::U64((u32::MAX as u64) + 1)).is_err());
892+
assert!(f32::coerce(PValue::U64(u64::MAX)).is_err());
893+
894+
// Test smaller types still work
895+
assert!(f32::coerce(PValue::U8(255)).is_ok());
896+
assert!(f32::coerce(PValue::U16(u16::MAX)).is_ok());
897+
assert!(f32::coerce(PValue::U32(u32::MAX)).is_ok());
898+
}
899+
900+
#[test]
901+
fn test_coerce_f64_all_unsigned() {
902+
// Test f64 can accept all unsigned integer values as bit patterns
903+
assert!(f64::coerce(PValue::U8(u8::MAX)).is_ok());
904+
assert!(f64::coerce(PValue::U16(u16::MAX)).is_ok());
905+
assert!(f64::coerce(PValue::U32(u32::MAX)).is_ok());
906+
assert!(f64::coerce(PValue::U64(u64::MAX)).is_ok());
907+
908+
// Verify specific bit patterns
909+
assert_eq!(
910+
f64::coerce(PValue::U64(0x3ff0000000000000)).unwrap(),
911+
1.0f64 // 0x3ff0000000000000 is 1.0 in f64
912+
);
913+
}
837914
}

vortex-scalar/src/tests/casting.rs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -267,9 +267,8 @@ mod tests {
267267
}
268268
}
269269

270-
// TODO(robert): This test should panic (it is storing a garbage value).
271270
#[test]
272-
// #[should_panic]
271+
#[should_panic]
273272
fn test_coercion_with_overflow_protection() {
274273
// Test that values too large for target type are not coerced
275274
let large_u64 = u64::MAX;

0 commit comments

Comments
 (0)