Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ public class NativeArrayBuffer extends ScriptableObject {
private static final byte[] EMPTY_BUF = new byte[0];

byte[] buffer;
// ES2024: maxByteLength for resizable buffers (-1 = fixed-length)
private int maxByteLength = -1;

@Override
public String getClassName() {
Expand All @@ -50,8 +52,12 @@ public static Object init(Context cx, Scriptable scope, boolean sealed) {
constructor.definePrototypeMethod(scope, "transfer", 0, NativeArrayBuffer::js_transfer);
constructor.definePrototypeMethod(
scope, "transferToFixedLength", 0, NativeArrayBuffer::js_transferToFixedLength);
constructor.definePrototypeMethod(scope, "resize", 1, NativeArrayBuffer::js_resize);
constructor.definePrototypeProperty(cx, "byteLength", NativeArrayBuffer::js_byteLength);
constructor.definePrototypeProperty(cx, "detached", NativeArrayBuffer::js_detached);
constructor.definePrototypeProperty(cx, "resizable", NativeArrayBuffer::js_resizable);
constructor.definePrototypeProperty(
cx, "maxByteLength", NativeArrayBuffer::js_maxByteLength);
constructor.definePrototypeProperty(
SymbolKey.TO_STRING_TAG, "ArrayBuffer", DONTENUM | READONLY);

Expand Down Expand Up @@ -144,7 +150,41 @@ private static NativeArrayBuffer getSelf(Scriptable thisObj) {

private static NativeArrayBuffer js_constructor(Context cx, Scriptable scope, Object[] args) {
double length = isArg(args, 0) ? ScriptRuntime.toNumber(args[0]) : 0;
return new NativeArrayBuffer(length);

// ES2024: Check for options parameter with maxByteLength
int maxByteLength = -1;
if (isArg(args, 1) && args[1] instanceof Scriptable) {
Scriptable options = (Scriptable) args[1];
Object maxByteLengthValue = ScriptableObject.getProperty(options, "maxByteLength");
if (maxByteLengthValue != Scriptable.NOT_FOUND
&& !Undefined.instance.equals(maxByteLengthValue)) {
double maxLen = ScriptRuntime.toNumber(maxByteLengthValue);

// Validate maxByteLength
if (Double.isNaN(maxLen)) {
maxLen = 0;
}
if (maxLen < 0 || Double.isInfinite(maxLen)) {
throw ScriptRuntime.rangeError("Invalid maxByteLength");
}
if (maxLen >= Integer.MAX_VALUE) {
throw ScriptRuntime.rangeError("maxByteLength too large");
}

int maxLenInt = (int) maxLen;

// Check that length <= maxByteLength
if (length > maxLenInt) {
throw ScriptRuntime.rangeError("ArrayBuffer length exceeds maxByteLength");
}

maxByteLength = maxLenInt;
}
}

NativeArrayBuffer buffer = new NativeArrayBuffer(length);
buffer.maxByteLength = maxByteLength;
return buffer;
}

private static Boolean js_isView(
Expand Down Expand Up @@ -352,4 +392,108 @@ private static int validateNewByteLength(Object[] args, int defaultLength) {

return (int) newLength;
}

// ES2024 ArrayBuffer.prototype.resize
private static Object js_resize(
Context cx, Scriptable scope, Scriptable thisObj, Object[] args) {
NativeArrayBuffer self = getSelf(thisObj);

// 1. Let O be the this value
// 2. Perform ? RequireInternalSlot(O, [[ArrayBufferData]])
// (getSelf handles this validation)

// 3. If IsDetachedBuffer(O) is true, throw a TypeError exception
if (self.isDetached()) {
throw ScriptRuntime.typeErrorById("msg.arraybuf.detached");
}

// 4. If IsResizableArrayBuffer(O) is false, throw a TypeError exception
if (self.maxByteLength < 0) {
throw ScriptRuntime.typeError("ArrayBuffer is not resizable");
}

// 5. Let newByteLength be ? ToIntegerOrInfinity(newLength)
if (!isArg(args, 0)) {
throw ScriptRuntime.typeError("Missing required argument: newLength");
}

double newLengthDouble = ScriptRuntime.toNumber(args[0]);

// ToIntegerOrInfinity: Handle NaN (convert to 0)
if (Double.isNaN(newLengthDouble)) {
newLengthDouble = 0;
}

// 6. If newByteLength < 0 or newByteLength > O.[[ArrayBufferMaxByteLength]], throw a
// RangeError exception
if (newLengthDouble < 0 || Double.isInfinite(newLengthDouble)) {
throw ScriptRuntime.rangeError("Invalid newLength");
}

if (newLengthDouble >= Integer.MAX_VALUE) {
throw ScriptRuntime.rangeError("newLength too large");
}

int newLength = (int) newLengthDouble;

if (newLength > self.maxByteLength) {
throw ScriptRuntime.rangeError(
"newLength exceeds maxByteLength (" + self.maxByteLength + ")");
}

// 7. Let hostHandled be ? HostResizeArrayBuffer(O, newByteLength)
// 8. If hostHandled is handled, return undefined
// 9. Let oldBlock be O.[[ArrayBufferData]]
// 10. Let newBlock be ? CreateByteDataBlock(newByteLength)
// 11. Let copyLength be min(newByteLength, O.[[ArrayBufferByteLength]])
// 12. Perform CopyDataBlockBytes(newBlock, 0, oldBlock, 0, copyLength)
// 13. NOTE: Neither creation of the new Data Block nor copying from the old Data Block
// are observable
// 14. Set O.[[ArrayBufferData]] to newBlock
// 15. Set O.[[ArrayBufferByteLength]] to newByteLength

int oldLength = self.getLength();
if (newLength == oldLength) {
// No resize needed
return Undefined.instance;
}

byte[] newBuffer = new byte[newLength];
int copyLength = Math.min(newLength, oldLength);

// Copy existing data
if (copyLength > 0) {
System.arraycopy(self.buffer, 0, newBuffer, 0, copyLength);
}

// New bytes are automatically initialized to 0 in Java
self.buffer = newBuffer;

// 16. Return undefined
return Undefined.instance;
}

/** Return true if this ArrayBuffer is resizable (ES2024). */
public boolean isResizable() {
return maxByteLength >= 0;
}

// ES2024 ArrayBuffer.prototype.resizable getter
private static Object js_resizable(Scriptable thisObj) {
NativeArrayBuffer self = getSelf(thisObj);
// A buffer is resizable if maxByteLength was specified in constructor
return self.isResizable();
}

// ES2024 ArrayBuffer.prototype.maxByteLength getter
private static Object js_maxByteLength(Scriptable thisObj) {
NativeArrayBuffer self = getSelf(thisObj);
// For fixed-length buffers, maxByteLength = byteLength
// For resizable buffers, return the maxByteLength
if (self.maxByteLength >= 0) {
return self.maxByteLength;
} else {
return self.getLength();
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -48,17 +48,63 @@ public abstract class NativeTypedArrayView<T> extends NativeArrayBufferView
implements List<T>, RandomAccess, ExternalArrayData {
private static final long serialVersionUID = -4963053773152251274L;

/** The length, in elements, of the array */
protected final int length;
/**
* The length in elements of the array. For auto-length views (ES2025), this is recomputed
* dynamically when the backing resizable buffer changes size.
*/
protected int length;

/**
* True if this is an auto-length view (ES2025). Auto-length views are created when a TypedArray
* is constructed on a resizable ArrayBuffer without specifying a length.
*/
private final boolean isAutoLength;

protected NativeTypedArrayView() {
super();
length = 0;
isAutoLength = false;
}

/**
* Construct a TypedArray view over an ArrayBuffer. If len is negative, creates an auto-length
* view (ES2025) that tracks the buffer's size dynamically.
*/
protected NativeTypedArrayView(NativeArrayBuffer ab, int off, int len, int byteLen) {
super(ab, off, byteLen);
length = len;
this.isAutoLength = (len < 0);

if (isAutoLength) {
updateLength();
} else {
this.length = len;
}
}

/**
* Recomputes the length for auto-length views (ES2025). For fixed-length views, this is a
* no-op. For auto-length views, calculates the element count based on the current buffer size
* and offset.
*/
protected void updateLength() {
if (!isAutoLength) {
return;
}

if (arrayBuffer == null || arrayBuffer.isDetached()) {
length = 0;
return;
}

int bufferByteLength = arrayBuffer.getLength();
int availableBytes = bufferByteLength - offset;

if (availableBytes < 0) {
length = 0;
return;
}

length = availableBytes / getBytesPerElement();
}

// Array properties implementation.
Expand Down Expand Up @@ -206,7 +252,8 @@ static void init(
proto,
null,
(lcx, ls, largs) -> {
throw ScriptRuntime.typeError("Fuck");
throw ScriptRuntime.typeError(
"TypedArray constructor cannot be invoked directly");
});
proto.defineProperty("constructor", ta, DONTENUM);
defineProtoProperty(ta, cx, "buffer", NativeTypedArrayView::js_buffer, null);
Expand Down Expand Up @@ -341,6 +388,28 @@ protected interface RealThis {
NativeTypedArrayView<?> realThis(Scriptable thisObj);
}

/**
* Determines the view length for TypedArray construction. Returns -1 for auto-length views
* (ES2025), which only applies to resizable buffers when length is omitted.
*/
private static int determineViewLength(
NativeArrayBuffer buffer,
int explicitLength,
int calculatedByteLength,
int bytesPerElement,
Object[] args) {

if (isArg(args, 2)) {
return explicitLength;
}

if (buffer.isResizable()) {
return -1; // Auto-length view
} else {
return calculatedByteLength / bytesPerElement;
}
}

protected static NativeTypedArrayView<?> js_constructor(
Context cx,
Scriptable scope,
Expand Down Expand Up @@ -419,7 +488,10 @@ protected static NativeTypedArrayView<?> js_constructor(
throw ScriptRuntime.rangeErrorById("msg.typed.array.bad.offset", byteOff);
}

return constructable.construct(na, byteOff, newByteLength / bytesPerElement);
// Determine view length: -1 for auto-length (resizable buffers only)
int viewLength =
determineViewLength(na, newLength, newByteLength, bytesPerElement, args);
return constructable.construct(na, byteOff, viewLength);
}

if (arg0 instanceof NativeArray) {
Expand Down Expand Up @@ -523,8 +595,25 @@ private void setRange(Context cx, Scriptable scope, Scriptable source, double db
}
}

/**
* Check if this TypedArray view is out of bounds (ES2025). For auto-length views, updates the
* length before checking. Returns true if the buffer is detached or the offset is out of range.
*/
public boolean isTypedArrayOutOfBounds() {
return arrayBuffer.isDetached() || outOfRange;
updateLength();
if (arrayBuffer.isDetached() || outOfRange) {
return true;
}

// For fixed-length views on resizable buffers, check if the buffer
// has been resized such that the view is now out of bounds
if (!isAutoLength && arrayBuffer.isResizable()) {
int bufferByteLength = arrayBuffer.getLength();
int requiredByteLength = offset + (length * getBytesPerElement());
return offset > bufferByteLength || requiredByteLength > bufferByteLength;
}

return false;
}

/**
Expand Down Expand Up @@ -578,6 +667,7 @@ private static Object js_byteOffset(Scriptable thisObj) {

private static Object js_length(Scriptable thisObj) {
NativeTypedArrayView<?> o = realThis(thisObj);
// updateLength() is called by isTypedArrayOutOfBounds()
if (o.isTypedArrayOutOfBounds()) {
return 0;
}
Expand Down Expand Up @@ -1126,15 +1216,16 @@ private static Object js_subarray(

private static Object js_at(Context cx, Scriptable scope, Scriptable thisObj, Object[] args) {
NativeTypedArrayView<?> self = realThis(thisObj);
long len = self.validateAndGetLength();

long relativeIndex = 0;
if (args.length >= 1) {
relativeIndex = (long) ScriptRuntime.toInteger(args[0]);
}

long k = (relativeIndex >= 0) ? relativeIndex : self.length + relativeIndex;
long k = (relativeIndex >= 0) ? relativeIndex : len + relativeIndex;

if ((k < 0) || (k >= self.length)) {
if ((k < 0) || (k >= len)) {
return Undefined.instance;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,6 @@ public class Test262SuiteTest {
"object-rest",
"regexp-dotall",
"regexp-unicode-property-escapes",
"resizable-arraybuffer",
"SharedArrayBuffer",
"tail-call-optimization",
"Temporal",
Expand Down
Loading