mirror of
https://github.com/vercel-labs/scriptc.git
synced 2026-10-26 06:34:20 +08:00
Check for a class id before reading a stream's vtable word
The stream probes for untyped values (pipe destinations, pipeline stages, readline input and output, and the checks against the stream classes) read the vtable word of any boxed object. Instances with a class id in the rc word have no vtable word, so a hierarchy instance whose first field is a number crashed those probes. scr_stream_vt_of answers NULL for them, and each probe treats that as no stream.
This commit is contained in:
+6
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"order": [
|
||||
"<repo>/tests/corpus/class-id-instances-as-streams.ts"
|
||||
],
|
||||
"diags": []
|
||||
}
|
||||
@@ -140,9 +140,11 @@ static void *bcs_capsule_retain(void *p) { return scr_stream_retain(p); }
|
||||
/* Also the capsule's tag. */
|
||||
static void bcs_capsule_release(void *p) { scr_stream_release(p); }
|
||||
|
||||
static bool bcs_stream_vt(const ScrVt *vt) {
|
||||
return (vt->pre >= scr_readable_vt.pre && vt->pre <= scr_readable_vt.post) ||
|
||||
(vt->pre >= scr_writable_vt.pre && vt->pre <= scr_writable_vt.post);
|
||||
/* Whether `stream`, boxed as an "object:" typed ref, is a runtime stream. */
|
||||
static bool bcs_stream_vt(const ScrStream *stream) {
|
||||
const ScrVt *vt = scr_stream_vt_of(stream);
|
||||
return vt != NULL && ((vt->pre >= scr_readable_vt.pre && vt->pre <= scr_readable_vt.post) ||
|
||||
(vt->pre >= scr_writable_vt.pre && vt->pre <= scr_writable_vt.post));
|
||||
}
|
||||
|
||||
static void bcs_rec_release(void *p);
|
||||
@@ -154,7 +156,7 @@ static ScrStream *bcs_of(const ScrDyn *value) {
|
||||
if (value != NULL && value->kind == SCR_DYN_TYPED_REF && value->v.typed_ref.type_key_len > 7 &&
|
||||
memcmp(value->v.typed_ref.type_key, "object:", 7) == 0) {
|
||||
ScrStream *stream = value->v.typed_ref.ptr;
|
||||
return bcs_stream_vt(stream->vt) && scr_stream_native(stream, bcs_rec_release) ? stream : NULL;
|
||||
return bcs_stream_vt(stream) && scr_stream_native(stream, bcs_rec_release) ? stream : NULL;
|
||||
}
|
||||
if (value == NULL || value->kind != SCR_DYN_OBJ || value->symbol_properties == NULL) return NULL;
|
||||
ScrDyn *capsule = scr_dyn_obj_get(value->symbol_properties, bcs_hidden_key, BCS_HIDDEN_LEN);
|
||||
@@ -184,7 +186,7 @@ static ScrStream *bcs_native_of(const ScrDyn *value) {
|
||||
memcmp(value->v.typed_ref.type_key, "object:", 7) != 0)
|
||||
return NULL;
|
||||
ScrStream *stream = scr_dyn_typed_ref_unbox(value);
|
||||
if (bcs_stream_vt(stream->vt)) return stream;
|
||||
if (bcs_stream_vt(stream)) return stream;
|
||||
value->v.typed_ref.release(stream);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
@@ -4740,12 +4740,14 @@ ScrStream *scr_stream_rl_from_dyn(const ScrDyn *value, bool output) {
|
||||
if (value->kind == SCR_DYN_TYPED_REF && value->v.typed_ref.type_key_len > 7 &&
|
||||
!memcmp(value->v.typed_ref.type_key, "object:", 7)) {
|
||||
ScrStream *stream = scr_dyn_typed_ref_unbox(value);
|
||||
const ScrVt *vt = stream->vt;
|
||||
bool duplex = vt == &scr_duplex_vt || vt == &scr_transform_vt || vt == &scr_passthrough_vt ||
|
||||
scr_vt_within(vt, &scr_duplex_vt);
|
||||
bool ok = duplex || (output ? vt == &scr_writable_vt || scr_vt_within(vt, &scr_writable_vt)
|
||||
: vt == &scr_readable_vt || scr_vt_within(vt, &scr_readable_vt));
|
||||
if (ok) return stream;
|
||||
const ScrVt *vt = scr_stream_vt_of(stream);
|
||||
if (vt) {
|
||||
bool duplex = vt == &scr_duplex_vt || vt == &scr_transform_vt || vt == &scr_passthrough_vt ||
|
||||
scr_vt_within(vt, &scr_duplex_vt);
|
||||
bool ok = duplex || (output ? vt == &scr_writable_vt || scr_vt_within(vt, &scr_writable_vt)
|
||||
: vt == &scr_readable_vt || scr_vt_within(vt, &scr_readable_vt));
|
||||
if (ok) return stream;
|
||||
}
|
||||
value->v.typed_ref.release(stream);
|
||||
}
|
||||
static const char in_m[] = "createInterface with an input that is not a readable stream has no lowering";
|
||||
@@ -6411,9 +6413,7 @@ static ScrStream *scr_stream_view_receiver(const ScrDyn *value) {
|
||||
if (value->kind == SCR_DYN_TYPED_REF && value->v.typed_ref.type_key_len > 7 &&
|
||||
!memcmp(value->v.typed_ref.type_key, "object:", 7)) {
|
||||
ScrStream *stream = scr_dyn_typed_ref_unbox(value);
|
||||
/* A class instance with a class id in its rc word (scr_rc.h) has no
|
||||
* vtable word: its class is no stream. */
|
||||
const ScrVt *vt = scr_rc_class_id(stream) == 0 ? stream->vt : NULL;
|
||||
const ScrVt *vt = scr_stream_vt_of(stream);
|
||||
if (vt && ((vt->pre >= scr_readable_vt.pre && vt->pre <= scr_readable_vt.post) ||
|
||||
(vt->pre >= scr_writable_vt.pre && vt->pre <= scr_writable_vt.post))) return stream;
|
||||
value->v.typed_ref.release(stream);
|
||||
@@ -6718,9 +6718,9 @@ ScrStream *scr_stream_unbox_dyn(const ScrDyn *value, const ScrStr *message) {
|
||||
if (value->kind == SCR_DYN_TYPED_REF && value->v.typed_ref.type_key_len > 7 &&
|
||||
!memcmp(value->v.typed_ref.type_key, "object:", 7)) {
|
||||
ScrStream *stream = scr_dyn_typed_ref_unbox(value);
|
||||
const ScrVt *vt = stream->vt;
|
||||
if ((vt->pre >= scr_readable_vt.pre && vt->pre <= scr_readable_vt.post) ||
|
||||
(vt->pre >= scr_writable_vt.pre && vt->pre <= scr_writable_vt.post)) return stream;
|
||||
const ScrVt *vt = scr_stream_vt_of(stream);
|
||||
if (vt && ((vt->pre >= scr_readable_vt.pre && vt->pre <= scr_readable_vt.post) ||
|
||||
(vt->pre >= scr_writable_vt.pre && vt->pre <= scr_writable_vt.post))) return stream;
|
||||
value->v.typed_ref.release(stream);
|
||||
}
|
||||
scr_throw_error_msg_code(SCR_ERR_ERROR, message->data, message->len, "SC2020");
|
||||
|
||||
@@ -48,6 +48,13 @@ typedef struct ScrStream {
|
||||
ScrStreamState *st;
|
||||
} ScrStream;
|
||||
|
||||
/* The vtable of an object boxed as an "object:" typed ref, which may be any
|
||||
* program class instance: NULL for one with a class id in its rc word
|
||||
* (scr_rc.h), which has no vtable word and is no stream. */
|
||||
static inline const ScrVt *scr_stream_vt_of(const ScrStream *s) {
|
||||
return scr_rc_class_id(s) == 0 ? s->vt : NULL;
|
||||
}
|
||||
|
||||
extern SCR_TL ScrVt scr_readable_vt, scr_writable_vt, scr_duplex_vt,
|
||||
scr_transform_vt, scr_passthrough_vt; /* main() stamps pre/post */
|
||||
|
||||
|
||||
@@ -23,7 +23,11 @@ ScrStream *scr_stream_dyn_native(const ScrDyn *value, double side) {
|
||||
memcmp(value->v.typed_ref.type_key, "object:", 7) != 0)
|
||||
return NULL;
|
||||
ScrStream *stream = scr_dyn_typed_ref_unbox(value);
|
||||
const ScrVt *vt = stream->vt;
|
||||
const ScrVt *vt = scr_stream_vt_of(stream);
|
||||
if (vt == NULL) {
|
||||
value->v.typed_ref.release(stream);
|
||||
return NULL;
|
||||
}
|
||||
bool duplex = scr_stream_dyn_within(vt, &scr_duplex_vt) ||
|
||||
scr_stream_dyn_within(vt, &scr_transform_vt) ||
|
||||
scr_stream_dyn_within(vt, &scr_passthrough_vt);
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
// Instances of a class hierarchy, held as untyped values, where the stream
|
||||
// adapters probe for a native stream: pipe, pipeline, readline, and
|
||||
// instanceof checks against the stream classes. Their first field is a
|
||||
// number, which sits where a stream's vtable word would be.
|
||||
import { Duplex, PassThrough, Readable, Transform, Writable, pipeline } from "node:stream";
|
||||
import { createInterface } from "node:readline";
|
||||
|
||||
class Shape {
|
||||
size = 3.5;
|
||||
area(): number {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
class Square extends Shape {
|
||||
override area(): number {
|
||||
return this.size * this.size;
|
||||
}
|
||||
}
|
||||
|
||||
function attempt(label: string, run: () => unknown): void {
|
||||
try {
|
||||
run();
|
||||
console.log(label, "ok");
|
||||
} catch (error) {
|
||||
console.log(label, "throws", error instanceof Error);
|
||||
}
|
||||
}
|
||||
|
||||
const shape: any = new Square();
|
||||
try {
|
||||
Readable.from(["a"]).pipe(shape);
|
||||
} catch (error) {
|
||||
console.log("pipe", (error as Error).name);
|
||||
}
|
||||
attempt("pipeline", () => pipeline(shape, new PassThrough(), () => {}));
|
||||
attempt("readline-input", () => createInterface({ input: shape }));
|
||||
console.log("instanceof", shape instanceof Readable, shape instanceof Writable, shape instanceof Duplex);
|
||||
console.log("instanceof-transform", shape instanceof Transform, shape instanceof PassThrough);
|
||||
console.log("area", (shape as Square).area());
|
||||
@@ -0,0 +1 @@
|
||||
{ "plain": 4000, "san": 4500 }
|
||||
Reference in New Issue
Block a user