fix: 修复 oxide_builtins 整数溢出 panic 点与安全转换

This commit is contained in:
2026-09-21 14:11:20 +00:00
parent b4d281d109
commit fea24b6d26
19 changed files with 291 additions and 91 deletions
+28 -4
View File
@@ -63,7 +63,12 @@ pub(crate) fn get_instant_epoch_ns(obj: &JsObject) -> Option<i128> {
return Some(unsafe { oxide_runtime_api::bigint_data(value) }.to_i128().unwrap_or(i128::MAX));
}
if value.is_int() || value.is_double() {
return Some(to_number(value) as i128);
let n = to_number(value);
// f64 转 i128:超大值饱和到 i128 边界。
if n.is_finite() && n >= i128::MIN as f64 && n <= i128::MAX as f64 {
return Some(n as i128);
}
return Some(i128::MAX);
}
None
}
@@ -403,9 +408,28 @@ pub(crate) fn plain_date_time_object_parts<H: VmHost>(
values[8] = values[8].clamp(0.0, 999.0);
}
let [year, month, day, hour, minute, second, millisecond, microsecond, nanosecond] = values;
let (year, month, day) = (year as i32, month as u32, day as u32);
let (hour, minute, second) = (hour as u32, minute as u32, second as u32);
let (millisecond, microsecond, nanosecond) = (millisecond as u32, microsecond as u32, nanosecond as u32);
// 分量值可能超出目标整型范围(f64 截断后仍超 i32/u32 上限),先做有界转换。
// 超出范围后 valid_iso_date / valid_plain_time 会拒绝,此处兜住 debug 模式 panic。
let year = if year.is_finite() && year >= i32::MIN as f64 && year <= i32::MAX as f64 {
year as i32
} else {
return Err(crate::error::create_range_error(vm, "invalid date-time component"));
};
let mut to_u32 = |v: f64| -> Result<u32, JsValue> {
if v.is_finite() && v >= 0.0 && v <= u32::MAX as f64 {
Ok(v as u32)
} else {
Err(crate::error::create_range_error(vm, "invalid date-time component"))
}
};
let month = to_u32(month)?;
let day = to_u32(day)?;
let hour = to_u32(hour)?;
let minute = to_u32(minute)?;
let second = to_u32(second)?;
let millisecond = to_u32(millisecond)?;
let microsecond = to_u32(microsecond)?;
let nanosecond = to_u32(nanosecond)?;
if !valid_iso_date(year, month, day)
|| (!ignore_time && !valid_plain_time(hour, minute, second, millisecond, microsecond, nanosecond))
{
@@ -147,18 +147,33 @@ pub fn plain_date_constructor<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResul
}
// 分量转换序:year → month → day;缺参归 undefined 经 number-only 路径
// 统一落 RangeError(日期分量无缺省值,缺参不是合法分量)。
let year = native_try!(temporal_number_component(
let year_raw = native_try!(temporal_number_component(
vm,
if args.len() > 1 { vm.reg(args[1]) } else { JsValue::undefined() },
)) as i32;
let month = native_try!(temporal_number_component(
));
let month_raw = native_try!(temporal_number_component(
vm,
if args.len() > 2 { vm.reg(args[2]) } else { JsValue::undefined() },
)) as u32;
let day = native_try!(temporal_number_component(
));
let day_raw = native_try!(temporal_number_component(
vm,
if args.len() > 3 { vm.reg(args[3]) } else { JsValue::undefined() },
)) as u32;
));
// f64 截断后可能超出目标整型范围,先做有界转换。
let year = if year_raw >= i32::MIN as f64 && year_raw <= i32::MAX as f64 {
year_raw as i32
} else {
return NativeResult::Err(crate::error::create_range_error(vm, "invalid year"));
};
let mut to_u32 = |v: f64| -> Result<u32, JsValue> {
if v >= 0.0 && v <= u32::MAX as f64 {
Ok(v as u32)
} else {
Err(crate::error::create_range_error(vm, "invalid date component"))
}
};
let month = native_try!(to_u32(month_raw));
let day = native_try!(to_u32(day_raw));
if !valid_iso_date(year, month, day) {
return NativeResult::Err(crate::error::create_range_error(vm, "invalid ISO date"));
}
@@ -66,15 +66,27 @@ pub fn plain_date_time_constructor<H: VmHost>(vm: &mut H, args: &[u8]) -> Native
return NativeResult::Err(crate::error::create_range_error(vm, "invalid date-time component"));
}
let year = year_value as i32;
let month = month_value as u32;
let day = day_value as u32;
let hour = hour_value as u32;
let minute = minute_value as u32;
let second = second_value as u32;
let millisecond = millisecond_value as u32;
let microsecond = microsecond_value as u32;
let nanosecond = nanosecond_value as u32;
// f64 截断后可能超出目标整型范围,先做有界转换。
let year = if year_value >= i32::MIN as f64 && year_value <= i32::MAX as f64 {
year_value as i32
} else {
return NativeResult::Err(crate::error::create_range_error(vm, "invalid year"));
};
let mut to_u32 = |v: f64| -> Result<u32, JsValue> {
if v >= 0.0 && v <= u32::MAX as f64 {
Ok(v as u32)
} else {
Err(crate::error::create_range_error(vm, "invalid date-time component"))
}
};
let month = native_try!(to_u32(month_value));
let day = native_try!(to_u32(day_value));
let hour = native_try!(to_u32(hour_value));
let minute = native_try!(to_u32(minute_value));
let second = native_try!(to_u32(second_value));
let millisecond = native_try!(to_u32(millisecond_value));
let microsecond = native_try!(to_u32(microsecond_value));
let nanosecond = native_try!(to_u32(nanosecond_value));
let non_negative = components[1..].iter().all(|value| *value >= 0.0);
if !non_negative
|| !valid_iso_date(year, month, day)
@@ -76,14 +76,29 @@ pub fn plain_month_day_constructor<H: VmHost>(vm: &mut H, args: &[u8]) -> Native
vm,
if args.len() > 2 { vm.reg(args[2]) } else { JsValue::undefined() },
));
// f64 截断后可能超出 u32 范围,先做有界转换。
let mut to_u32 = |v: f64| -> Result<u32, JsValue> {
if v >= 0.0 && v <= u32::MAX as f64 {
Ok(v as u32)
} else {
Err(crate::error::create_range_error(vm, "invalid date component"))
}
};
let month = native_try!(to_u32(month));
let day = native_try!(to_u32(day));
let calendar_raw = if args.len() > 3 { vm.reg(args[3]) } else { JsValue::undefined() };
let calendar = native_try!(temporal_constructor_calendar_id(vm, calendar_raw));
let ref_year = if args.len() > 4 && !vm.reg(args[4]).is_undefined() {
native_try!(temporal_number_component(vm, vm.reg(args[4]))) as i32
let ref_year_raw = if args.len() > 4 && !vm.reg(args[4]).is_undefined() {
native_try!(temporal_number_component(vm, vm.reg(args[4])))
} else {
1972
1972.0
};
if !valid_iso_date(ref_year, month as u32, day as u32) {
let ref_year = if ref_year_raw >= i32::MIN as f64 && ref_year_raw <= i32::MAX as f64 {
ref_year_raw as i32
} else {
return NativeResult::Err(crate::error::create_range_error(vm, "invalid year"));
};
if !valid_iso_date(ref_year, month, day) {
return NativeResult::Err(crate::error::create_range_error(vm, "invalid ISO date"));
}
// 表示范围(ISODateWithinLimits):-271821-04-19 … +275760-09-13。
@@ -97,8 +112,8 @@ pub fn plain_month_day_constructor<H: VmHost>(vm: &mut H, args: &[u8]) -> Native
args,
JsObject::OBJ_TYPE_PLAIN_MONTH_DAY,
[
JsValue::float(month),
JsValue::float(day),
JsValue::float(month as f64),
JsValue::float(day as f64),
JsValue::float(ref_year as f64),
calendar_value,
],
@@ -516,7 +531,10 @@ pub fn plain_month_day_from<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult
}
code
} else {
let f = month_f.expect("month or monthCode is required checked above");
let f = match month_f {
Some(v) => v,
None => return NativeResult::Err(crate::error::create_type_error(vm, "month is required")),
};
if f < 1.0 {
return NativeResult::Err(crate::error::create_range_error(vm, "invalid month"));
}
@@ -43,15 +43,29 @@ fn is_ambiguous_date_string(s: &str) -> bool {
};
match s.len() {
4 if all_digits(0..4) => {
let (month, day) = (two_digits(0).unwrap(), two_digits(2).unwrap());
day_in_month(month, day)
match (two_digits(0), two_digits(2)) {
(Some(month), Some(day)) => day_in_month(month, day),
_ => false,
}
}
6 if all_digits(0..6) => {
match two_digits(4) {
Some(m) => (1..=12).contains(&m),
None => false,
}
}
6 if all_digits(0..6) => (1..=12).contains(&two_digits(4).unwrap()),
5 if bytes[2] == b'-' && all_digits(0..2) && all_digits(3..5) => {
let (month, day) = (two_digits(0).unwrap(), two_digits(3).unwrap());
day_in_month(month, day)
match (two_digits(0), two_digits(3)) {
(Some(month), Some(day)) => day_in_month(month, day),
_ => false,
}
}
7 if bytes[4] == b'-' && all_digits(0..4) && all_digits(5..7) => {
match two_digits(5) {
Some(m) => (1..=12).contains(&m),
None => false,
}
}
7 if bytes[4] == b'-' && all_digits(0..4) && all_digits(5..7) => (1..=12).contains(&two_digits(5).unwrap()),
_ => false,
}
}
@@ -297,12 +311,20 @@ pub fn plain_time_constructor<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResul
{
return NativeResult::Err(crate::error::create_range_error(vm, "invalid time component"));
}
let hour = hour_value as u32;
let minute = minute_value as u32;
let second = second_value as u32;
let ms = ms_value as u32;
let us = us_value as u32;
let ns = ns_value as u32;
// f64 截断后可能超出 u32 范围,先做有界转换。
let mut to_u32 = |v: f64| -> Result<u32, JsValue> {
if v >= 0.0 && v <= u32::MAX as f64 {
Ok(v as u32)
} else {
Err(crate::error::create_range_error(vm, "invalid time component"))
}
};
let hour = native_try!(to_u32(hour_value));
let minute = native_try!(to_u32(minute_value));
let second = native_try!(to_u32(second_value));
let ms = native_try!(to_u32(ms_value));
let us = native_try!(to_u32(us_value));
let ns = native_try!(to_u32(ns_value));
if !valid_plain_time(hour, minute, second, ms, us, ns) {
return NativeResult::Err(crate::error::create_range_error(vm, "invalid time component"));
}
@@ -398,7 +398,12 @@ pub fn plain_year_month_from<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult
None => (None, false),
};
// year 转换(TypeError/RangeError)在 monthCode 语法之后。
let year = native_try!(temporal_number_component(vm, year_raw)) as i32;
let year_raw = native_try!(temporal_number_component(vm, year_raw));
let year = if year_raw >= i32::MIN as f64 && year_raw <= i32::MAX as f64 {
year_raw as i32
} else {
return NativeResult::Err(crate::error::create_range_error(vm, "invalid year"));
};
let month_f = match month_raw.is_undefined() {
true => None,
false => Some(native_try!(temporal_number_component(vm, month_raw))),
@@ -419,7 +424,10 @@ pub fn plain_year_month_from<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult
}
code
} else {
let f = month_f.expect("month or monthCode is required checked above");
let f = match month_f {
Some(v) => v,
None => return NativeResult::Err(crate::error::create_type_error(vm, "month is required")),
};
if f < 1.0 {
return NativeResult::Err(crate::error::create_range_error(vm, "invalid month"));
}
@@ -530,7 +538,12 @@ fn year_month_like_parts<H: VmHost>(
None => (None, false),
};
// year 转换(TypeError/RangeError)在 monthCode 语法之后。
let year = temporal_number_component(vm, year_raw)? as i32;
let year_raw = temporal_number_component(vm, year_raw)?;
let year = if year_raw >= i32::MIN as f64 && year_raw <= i32::MAX as f64 {
year_raw as i32
} else {
return Err(crate::error::create_range_error(vm, "invalid year"));
};
let month_f = match month_raw.is_undefined() {
true => None,
false => Some(temporal_number_component(vm, month_raw)?),
@@ -551,7 +564,7 @@ fn year_month_like_parts<H: VmHost>(
}
code
} else {
let f = month_f.expect("month or monthCode is required checked above");
let f = month_f.ok_or_else(|| crate::error::create_type_error(vm, "month is required"))?;
if f < 1.0 {
return Err(crate::error::create_range_error(vm, "invalid month"));
}