Keep DataView's own behavior through unknown

A DataView reached as a checked-dynamic value behaved like a Uint8Array
over its bytes: it printed as `Uint8Array(2) [ 0, 0 ]`, its string and
tag were the typed array's, `.length` and index reads answered from the
bytes, and typed array methods ran on it. It now inspects as Node's
`DataView { [byteLength], [byteOffset], [buffer] }`, answers
"[object DataView]", has no length or indexed elements, and throws
"is not a function" for typed array methods.
This commit is contained in:
Chris Tate
2026-10-19 23:28:21 -05:00
parent 783870dec3
commit bc9ef21b90
10 changed files with 146 additions and 8 deletions
+5 -5
View File
@@ -3265,8 +3265,9 @@ export class LlDyn {
}
B.startBlock(labels.get(DYN_KIND.BYTES)!);
{
// Buffer-flavored values coerce utf8; plain Uint8Array joins
// its elements.
// Buffer-flavored values coerce utf8; typed arrays join their
// elements and a DataView answers "[object DataView]" (the
// runtime's conversion).
const bufp = B.tmp();
const bufRaw = B.tmp();
const isBuf = B.tmp();
@@ -3288,10 +3289,9 @@ export class LlDyn {
B.line(`call void @scr_str_release(ptr ${txt})`);
B.br(done);
B.startBlock(lJoin);
const bts = this.payloadOf(B, "%d", "ptr");
host.declare(`declare ptr @scr_bytes_join(ptr, ptr)`);
host.declare(`declare ptr @scr_dyn_to_string(ptr, ptr)`);
const joined = B.tmp();
B.line(`${joined} = call ptr @scr_bytes_join(ptr ${bts}, ptr ${host.internLiteral(",")})`);
B.line(`${joined} = call ptr @scr_dyn_to_string(ptr %d, ptr null)`);
this.putScrStr(B, "%b", joined);
B.line(`call void @scr_str_release(ptr ${joined})`);
B.br(done);
@@ -0,0 +1,6 @@
{
"order": [
"<repo>/tests/corpus/6346-inspect-dyn-dataview.ts"
],
"diags": []
}
@@ -0,0 +1,6 @@
{
"order": [
"<repo>/tests/corpus/6347-dataview-methods-js.cjs"
],
"diags": []
}
+11
View File
@@ -1558,6 +1558,17 @@ static ScrDyn *scr_dyn_invoke_impl(
}
return scr_exc_pending() ? NULL : scr_dyn_retain(scr_dyn_undefined());
}
/* The typed array methods below are not DataView's; it inherits
* Object.prototype's. */
if (dyn_name_is(method, "toString") || dyn_name_is(method, "toLocaleString")) {
ScrStr *tag = scr_str_new("[object DataView]", 17);
ScrDyn *out = scr_dyn_new_str(tag);
scr_str_release(tag);
return out;
}
if (dyn_name_is(method, "valueOf")) return scr_dyn_retain(recv);
dyn_throw_not_fn(what);
return NULL;
}
size_t blen = bytes->len;
if (dyn_name_is(method, "at")) {
+36
View File
@@ -823,6 +823,41 @@ static ScrStr *scr_insp_array_buffer(const ScrBytes *h, const char *cls, double
return out;
}
/* DataView: formatValue's `DataView { [byteLength], [byteOffset],
* [buffer] }`, the buffer rendered one level deeper. `[DataView]` beyond
* depth. */
static ScrStr *scr_insp_data_view(const ScrBytes *b, double recurse, double depth) {
if (recurse > depth) return scr_str_new("[DataView]", 10);
const ScrBytes *owner = b->backing ? b->backing : b;
char line[64];
scr_insp_begin(recurse + 1);
int n = snprintf(line, sizeof line, "[byteLength]: %.0f", scr_bytes_byte_len(b));
ScrStr *entry = scr_str_new(line, (size_t)n);
scr_insp_entry(entry, false);
scr_str_release(entry);
n = snprintf(line, sizeof line, "[byteOffset]: %.0f", scr_bytes_byte_offset(b));
entry = scr_str_new(line, (size_t)n);
scr_insp_entry(entry, false);
scr_str_release(entry);
ScrStr *buffer = scr_insp_array_buffer(owner, owner->shared ? "SharedArrayBuffer" : "ArrayBuffer",
recurse + 1, depth);
InspBuf eb = {0};
ib_cstr(&eb, "[buffer]: ");
ib_bytes(&eb, buffer->data, buffer->len);
scr_str_release(buffer);
entry = ib_take(&eb);
scr_insp_entry(entry, false);
scr_str_release(entry);
ScrStr *base = scr_str_new("", 0);
ScrStr *b0 = scr_str_new("DataView {", 10);
ScrStr *b1 = scr_str_new("}", 1);
ScrStr *out = scr_insp_end(base, b0, b1, recurse + 1, false, false);
scr_str_release(base);
scr_str_release(b0);
scr_str_release(b1);
return out;
}
static ScrStr *scr_insp_dyn_error(ScrDyn *d, double recurse, double depth);
/* A runtime error through its node: the cached one, or one built from its
@@ -1542,6 +1577,7 @@ ScrStr *scr_insp_dyn(ScrDyn *d, double recurse, double depth) {
}
case SCR_DYN_BYTES: {
ScrBytes *b = d->v.bytes;
if (b->is_data_view) return scr_insp_data_view(b, recurse, depth);
if (d->buffer) {
/* Buffer-flavored (a stream chunk): Node's <Buffer ..> form. */
return scr_insp_buffer(b);
+14 -3
View File
@@ -3456,10 +3456,19 @@ ScrDyn *scr_bytes_construct(const ScrDyn *callee, const ScrDyn *args, const ScrS
/* Shared view metadata and indexed reads for both compiler backends. */
ScrDyn *scr_dyn_bytes_key_get(const ScrDyn *value, const ScrStr *key) {
ScrBytes *bytes = value->v.bytes;
if (key->len == 6 && memcmp(key->data, "length", 6) == 0) return scr_dyn_new_num(scr_bytes_len(bytes));
if (key->len == 10 && memcmp(key->data, "byteLength", 10) == 0) return scr_dyn_new_num(scr_bytes_byte_len(bytes));
if (key->len == 10 && memcmp(key->data, "byteOffset", 10) == 0) return scr_dyn_new_num(scr_bytes_byte_offset(bytes));
if (key->len == 6 && memcmp(key->data, "buffer", 6) == 0) return scr_array_buffer_from_bytes(bytes);
if (bytes->is_data_view) {
/* A DataView has no length or indexed elements. */
if (key->len == 11 && memcmp(key->data, "constructor", 11) == 0) {
static const char message[] = "DataView's constructor through unknown is not supported yet";
scr_throw_error_msg(SCR_ERR_ERROR, message, sizeof message - 1);
return NULL;
}
return scr_dyn_retain(scr_dyn_undefined());
}
if (key->len == 6 && memcmp(key->data, "length", 6) == 0) return scr_dyn_new_num(scr_bytes_len(bytes));
if (key->len == 11 && memcmp(key->data, "constructor", 11) == 0) {
if (!value->buffer) return scr_bytes_constructor_for(bytes->elem);
/* Buffer retains its separate builtin identity. */
@@ -6364,7 +6373,7 @@ ScrStr *scr_dyn_object_tag(const ScrDyn *d) {
ScrJsonBuf b;
scr_jb_init(&b);
scr_jb_puts(&b, "[object ");
scr_jb_puts(&b, scr_bytes_elem_name(d->v.bytes->elem));
scr_jb_puts(&b, d->v.bytes->is_data_view ? "DataView" : scr_bytes_elem_name(d->v.bytes->elem));
scr_jb_putc(&b, ']');
return scr_jb_finish(&b);
}
@@ -7059,6 +7068,8 @@ ScrStr *scr_dyn_to_string(const ScrDyn *d, const ScrStr *enc) {
return d->v.symbol.render(d->v.symbol.value);
case SCR_DYN_BYTES:
if (d->buffer) return scr_bytes_to_str(d->v.bytes, enc);
/* DataView has no toString of its own: Object.prototype.toString. */
if (d->v.bytes->is_data_view) return scr_str_new("[object DataView]", 17);
{
ScrStr *separator = scr_str_new(",", 1);
ScrStr *out = scr_bytes_join(d->v.bytes, separator);
@@ -8211,7 +8222,7 @@ static const char *scr_dyn_kind_name(const ScrDyn *d) {
case SCR_DYN_ARR: return "array";
case SCR_DYN_OBJ: return "object";
case SCR_DYN_UNDEF: return "undefined";
case SCR_DYN_BYTES: return scr_bytes_elem_name(d->v.bytes->elem);
case SCR_DYN_BYTES: return d->v.bytes->is_data_view ? "DataView" : scr_bytes_elem_name(d->v.bytes->elem);
case SCR_DYN_FUNC: return "function";
case SCR_DYN_HANDLE: return scr_dyn_handle_cls(d); /* "got IncomingMessage" */
case SCR_DYN_PROMISE: return "Promise"; /* "got Promise" */
+41
View File
@@ -0,0 +1,41 @@
// A DataView reached through `unknown` keeps its own behavior: Node's
// `DataView { [byteLength], [byteOffset], [buffer] }` inspection, the
// "[object DataView]" string and tag, no length or indexed elements, and
// no typed array methods.
import { inspect } from "node:util";
function show(label: string, value: unknown): void {
console.log(label, value);
}
const buf = new ArrayBuffer(8);
const whole = new DataView(buf);
whole.setUint16(0, 0x1234);
show("whole", whole);
show("offset", new DataView(buf, 2, 4));
show("empty", new DataView(new ArrayBuffer(0)));
show("big", new DataView(new ArrayBuffer(120)));
show("nested", { view: new DataView(buf, 6) });
show("shared", new DataView(new SharedArrayBuffer(2)));
const u: unknown = whole;
console.log(inspect({ a: { u } }, { depth: 1 }), inspect([u], { depth: 0 }));
console.log(String(u), Object.prototype.toString.call(u), `${u}`);
const view: unknown = new DataView(buf, 1);
const o = view as { [k: string]: unknown };
console.log(o.length, o[0], o.byteLength, o.byteOffset);
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const loose = view as any;
console.log(loose.toString(), loose.valueOf() === view);
for (const method of ["at", "join", "fill", "slice"]) {
try {
loose[method](0);
} catch (e) {
console.log(method, (e as Error).name);
}
}
console.log(view instanceof DataView, ArrayBuffer.isView(view));
// A typed array through `unknown` is unaffected.
const bytes: unknown = new Uint8Array([1, 2]);
console.log(bytes, String(bytes), `${bytes}`, (bytes as Uint8Array).length);
+25
View File
@@ -0,0 +1,25 @@
'use strict';
// A DataView held in an untyped binding answers its own methods and
// Object.prototype's, and none of the typed array methods.
const view = [new DataView(new ArrayBuffer(4))][0];
function attempt(label, f) {
try {
console.log(label, f());
} catch (e) {
console.log(label, `${e.name}: ${e.message}`);
}
}
function use(v) {
v.setUint16(0, 0x1234);
attempt('get', () => [v.getUint16(0), v.getUint8(1)].join(' '));
attempt('toString', () => v.toString());
attempt('toLocaleString', () => v.toLocaleString());
attempt('valueOf', () => v.valueOf() === v);
attempt('join', () => v.join('-'));
attempt('at', () => v.at(0));
attempt('fill', () => v.fill(1));
attempt('slice', () => v.slice(0, 1));
attempt('indexOf', () => v.indexOf(0));
attempt('string', () => `${String(v)} ${v}`);
}
use(view);
@@ -0,0 +1 @@
{ "plain": 1700, "san": 2200 }
@@ -0,0 +1 @@
{ "plain": 1500, "san": 2000 }