use ecmascript_atomics::Ordering;
use crate::{
ecmascript::{
Agent, BigInt, ExceptionType, JsResult, Number, Numeric, Value, Viewable,
is_fixed_length_array_buffer, require_internal_slot_data_view, set_value_in_buffer,
to_big_int, to_index, to_number, try_result_into_js, try_to_index,
},
engine::{Bindable, GcScope, Scopable},
};
use super::AnyDataView;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
struct ByteLength(pub usize);
impl ByteLength {
pub const fn value(value: usize) -> Self {
Self(value)
}
pub const fn detached() -> Self {
Self(usize::MAX)
}
pub(crate) fn is_detached(&self) -> bool {
*self == Self::detached()
}
}
#[derive(Debug, Clone)]
pub(crate) struct DataViewWithBufferWitnessRecord<'a> {
object: AnyDataView<'a>,
cached_buffer_byte_length: ByteLength,
}
#[inline(always)]
pub(crate) fn make_data_view_with_buffer_witness_record<'a>(
agent: &Agent,
obj: AnyDataView<'a>,
order: Ordering,
) -> DataViewWithBufferWitnessRecord<'a> {
let buffer = obj.viewed_array_buffer(agent);
let byte_length = if buffer.is_detached(agent) {
ByteLength::detached()
} else {
ByteLength::value(buffer.byte_length(agent, order))
};
DataViewWithBufferWitnessRecord {
object: obj,
cached_buffer_byte_length: byte_length,
}
}
#[inline(always)]
pub(crate) fn get_view_byte_length(
agent: &Agent,
view_record: &DataViewWithBufferWitnessRecord,
) -> usize {
assert!(!is_view_out_of_bounds(agent, view_record));
let view = view_record.object;
if let Some(byte_length) = view.byte_length(agent) {
return byte_length;
}
debug_assert!(!is_fixed_length_array_buffer(
agent,
view.viewed_array_buffer(agent)
));
let byte_offset = view.byte_offset(agent);
assert!(!view_record.cached_buffer_byte_length.is_detached());
let byte_length = view_record.cached_buffer_byte_length.0;
byte_length - byte_offset
}
#[inline(always)]
pub(crate) fn is_view_out_of_bounds(
agent: &Agent,
view_record: &DataViewWithBufferWitnessRecord,
) -> bool {
let view = view_record.object;
let ab = view.viewed_array_buffer(agent);
let byte_offset_start = view.byte_offset(agent);
let byte_length = view.byte_length(agent);
let buffer_byte_length = view_record.cached_buffer_byte_length;
assert!(ab.is_detached(agent) == buffer_byte_length.is_detached());
if buffer_byte_length.is_detached() {
return true;
}
let buffer_byte_length = buffer_byte_length.0;
let byte_offset_end = if let Some(byte_length) = byte_length {
byte_offset_start + byte_length
} else {
buffer_byte_length
};
if byte_offset_start > buffer_byte_length || byte_offset_end > buffer_byte_length {
return true;
}
false
}
pub(crate) fn get_view_value<'gc, T: Viewable>(
agent: &mut Agent,
view: Value,
request_index: Value,
is_little_endian: bool,
mut gc: GcScope<'gc, '_>,
) -> JsResult<'gc, T> {
let mut view = require_internal_slot_data_view(agent, view, gc.nogc())
.unbind()?
.bind(gc.nogc());
let get_index = if let Some(res) =
try_result_into_js(try_to_index(agent, request_index, gc.nogc())).unbind()?
{
res as usize
} else {
match get_view_value_scope_slow(agent, view.unbind(), request_index.unbind(), gc.reborrow())
{
Ok((v, res)) => {
view = v.unbind().bind(gc.nogc());
res
}
Err(err) => return Err(err.unbind().bind(gc.into_nogc())),
}
};
let view = view.unbind();
let gc = gc.into_nogc();
let view = view.bind(gc);
let view_offset = view.byte_offset(agent);
let view_record = make_data_view_with_buffer_witness_record(agent, view, Ordering::Unordered);
if is_view_out_of_bounds(agent, &view_record) {
return Err(agent.throw_exception_with_static_message(
ExceptionType::TypeError,
"DataView is out of bounds",
gc,
));
}
let view_size = get_view_byte_length(agent, &view_record);
let element_size = size_of::<T>();
if get_index + element_size > view_size {
return Err(agent.throw_exception_with_static_message(
ExceptionType::RangeError,
"Index out of bounds",
gc,
));
}
let buffer_index = get_index + view_offset;
let result = unsafe { view.get_value_from_buffer::<T>(agent, buffer_index) };
if is_little_endian && cfg!(target_endian = "big")
|| !is_little_endian && cfg!(target_endian = "little")
{
Ok(result.flip_endian())
} else {
Ok(result)
}
}
#[inline(never)]
#[cold]
fn get_view_value_scope_slow<'gc>(
agent: &mut Agent,
view: AnyDataView,
request_index: Value,
gc: GcScope<'gc, '_>,
) -> JsResult<'gc, (AnyDataView<'gc>, usize)> {
let scoped_view = view.scope(agent, gc.nogc());
let index = to_index(agent, request_index, gc).unbind()? as usize;
Ok((unsafe { scoped_view.take(agent) }, index))
}
pub(crate) fn set_view_value<'gc, T: Viewable>(
agent: &mut Agent,
view: Value,
request_index: Value,
is_little_endian: bool,
value: Value,
mut gc: GcScope<'gc, '_>,
) -> JsResult<'gc, Value<'gc>> {
let mut view = require_internal_slot_data_view(agent, view, gc.nogc())
.unbind()?
.bind(gc.nogc());
let get_index = if let Some(res) =
try_result_into_js(try_to_index(agent, request_index, gc.nogc())).unbind()?
{
res as usize
} else {
let scoped_view = view.scope(agent, gc.nogc());
let res = to_index(agent, request_index, gc.reborrow()).unbind()? as usize;
view = scoped_view.get(agent).bind(gc.nogc());
res
};
let number_value: Numeric = if T::IS_BIGINT {
if let Ok(v) = BigInt::try_from(value) {
v.into()
} else {
let scoped_view = view.scope(agent, gc.nogc());
let v = to_big_int(agent, value, gc.reborrow())
.unbind()?
.bind(gc.nogc());
view = scoped_view.get(agent).bind(gc.nogc());
v.into()
}
} else {
if let Ok(v) = Number::try_from(value) {
v.into()
} else {
let scoped_view = view.scope(agent, gc.nogc());
let v = to_number(agent, value, gc.reborrow())
.unbind()?
.bind(gc.nogc());
view = scoped_view.get(agent).bind(gc.nogc());
v.into()
}
};
let view = view.unbind();
let number_value = number_value.unbind();
let gc = gc.into_nogc();
let view = view.bind(gc);
let number_value = number_value.bind(gc);
let view_offset = view.byte_offset(agent);
let view_record = make_data_view_with_buffer_witness_record(agent, view, Ordering::Unordered);
if is_view_out_of_bounds(agent, &view_record) {
return Err(agent.throw_exception_with_static_message(
ExceptionType::TypeError,
"DataView is out of bounds",
gc,
));
}
let view_size = get_view_byte_length(agent, &view_record);
let element_size = size_of::<T>();
if get_index + element_size > view_size {
return Err(agent.throw_exception_with_static_message(
ExceptionType::RangeError,
"Index out of bounds",
gc,
));
}
let buffer_index = get_index + view_offset;
set_value_in_buffer::<T>(
agent,
view.viewed_array_buffer(agent),
buffer_index,
number_value,
false,
Ordering::Unordered,
Some(is_little_endian),
);
Ok(Value::Undefined)
}