Throw for DataView targets of borrowed typed array set calls

This commit is contained in:
Chris Tate
2026-10-23 16:28:38 -05:00
parent fa80286f9b
commit a9cedb9839
3 changed files with 49 additions and 3 deletions
@@ -927,8 +927,9 @@ function bytesSetIntrinsic(
/** `Uint8Array.prototype.set.call(target, src, offset?)`, through any typed
* array constructor's prototype (they share %TypedArray%.prototype.set). A
* typed-array target lowers like `target.set(src, offset)`. Any other
* target is checked at run time: a typed array calls its `set`, and
* target the checker types as a typed array or Buffer lowers like
* `target.set(src, offset)`. Any other target, a DataView included, is
* checked at run time: a typed array calls its `set`, and
* anything else throws Node's TypeError "this is not a typed array.",
* after every argument is evaluated. */
export function lowerBytesPrototypeSetCall(
@@ -961,7 +962,16 @@ export function lowerBytesPrototypeSetCall(
if (argNodes.length < 2 || argNodes.length > 3 || argNodes.some(ts.isSpreadElement)) return null;
const loc = locOf(call);
const target = lowerer.lowerExpr(argNodes[0]!);
if (target.type.kind === "bytes")
// A DataView lowers to the same bytes type, so the checker type decides:
// only a typed array (or a Buffer) takes the method's path.
const targetSymbol = lowerer.typeOf(argNodes[0]!).getSymbol();
if (
target.type.kind === "bytes" &&
targetSymbol !== undefined &&
lowerer.isStdlibSymbol(targetSymbol) &&
(own(BYTES_CTORS, ts.symbolName(targetSymbol)) !== undefined ||
ts.symbolName(targetSymbol) === "Buffer")
)
return bytesSetIntrinsic(lowerer, target, argNodes.slice(1), loc);
const slot = (value: IrExpr): { decl: IrStmt; read: IrExpr } => {
const local = lowerer.declareHiddenLocal("%setArg", DYN);
@@ -0,0 +1,6 @@
{
"order": [
"<repo>/tests/corpus/8100-typed-array-prototype-set-data-view.ts"
],
"diags": []
}
@@ -0,0 +1,30 @@
// A borrowed TypedArray.prototype.set over a DataView target throws Node's
// TypeError after evaluating its arguments, and leaves the view unchanged.
const view = new DataView(new ArrayBuffer(4));
const order: string[] = [];
function arg<T>(label: string, value: T): T {
order.push(label);
return value;
}
try {
Uint8Array.prototype.set.call(arg("target", view), arg("source", [1, 2]), arg("offset", 1));
console.log("set");
} catch (e) {
console.log(e instanceof TypeError, (e as Error).message, order.join(","));
}
console.log(view.getUint8(1), view.getUint8(2));
function fill(target: DataView | Uint8Array, bytes: number[]): string {
try {
Uint8Array.prototype.set.call(target, bytes);
return "set";
} catch (e) {
return (e as Error).message;
}
}
console.log(fill(view, [7]), view.getUint8(0));
const bytes = new Uint8Array(2);
console.log(fill(bytes, [7]), bytes[0]);
const buffer = Buffer.alloc(3);
Uint8Array.prototype.set.call(buffer, [4, 5], 1);
console.log(buffer.toString("hex"));