mirror of
https://github.com/vercel-labs/scriptc.git
synced 2026-10-27 06:43:19 +08:00
290 lines
15 KiB
C
290 lines
15 KiB
C
/* scriptc runtime — class hierarchies, class objects, and the Error,
|
|
* DOMException and AggregateError objects (scr_error.c, scr_object.c).
|
|
* Included by scr_runtime.h, which says where new declarations go. */
|
|
#ifndef SCR_ERROR_H
|
|
#define SCR_ERROR_H
|
|
|
|
#include "scr_string.h"
|
|
|
|
/* ── class hierarchies (single inheritance) ───────────────────────────
|
|
* Classes in an `extends` hierarchy share a two-word object prefix: the
|
|
* usual `size_t rc`, then a pointer to the class's static vtable (emitted
|
|
* per class by the compiler; standalone classes carry no vtable word and
|
|
* none of this applies). A derived class embeds its base's fields as a
|
|
* layout PREFIX, so an upcast is a pointer reinterpret and base-field
|
|
* offsets agree through any static type.
|
|
*
|
|
* The vtable begins with this header; the compiler-emitted concrete
|
|
* per-hierarchy struct appends one member per virtual method slot (only
|
|
* methods actually overridden somewhere get slots — never-overridden
|
|
* methods keep direct static calls). `pre`/`post` are the class's preorder
|
|
* interval in the whole-program class forest: `x instanceof C` is the O(1)
|
|
* range check `C.pre <= x->vt->pre && x->vt->pre <= C.post`. `release` is
|
|
* the class's own whole-object release: releasing through a base-typed
|
|
* pointer must tear down the DERIVED object's fields, so the emitted
|
|
* release of every hierarchy class dispatches through the stored vtable
|
|
* (retain needs no dispatch — rc is at offset 0 in every layout). The
|
|
* cycle collector needs no vtable involvement: a cycle-capable hierarchy
|
|
* object's header trace/teardown are stamped with the concrete class's
|
|
* functions at allocation, which already is dynamic dispatch. */
|
|
typedef struct ScrVt {
|
|
size_t pre, post;
|
|
void (*release)(void *obj);
|
|
} ScrVt;
|
|
|
|
/* ── class objects (classes as first-class values) ────────────────────
|
|
* Top-level classes use immortal templates. Evaluating a function-local
|
|
* class creates a fresh, traced object that owns its captured binding boxes.
|
|
* Every construct thunk takes the borrowed class object before source
|
|
* arguments. Instances of a local class retain that object for identity and
|
|
* method captures. `pre`/`post` still describe the compiled class hierarchy;
|
|
* local-class instanceof additionally compares the fresh object identity. */
|
|
typedef struct ScrClassObj {
|
|
size_t rc;
|
|
size_t pre, post;
|
|
void *ctor;
|
|
const ScrStr *name;
|
|
size_t ncaps;
|
|
size_t length;
|
|
struct ScrBox *prototype_data;
|
|
struct ScrBox *static_data;
|
|
struct ScrBox *caps[];
|
|
} ScrClassObj;
|
|
|
|
static inline ScrClassObj *scr_classobj_retain(ScrClassObj *c) {
|
|
if (c && c->rc != SIZE_MAX) {
|
|
c->rc++;
|
|
}
|
|
return c;
|
|
}
|
|
void scr_classobj_release(ScrClassObj *c);
|
|
void scr_classobj_clear_properties(ScrClassObj *c);
|
|
ScrClassObj *scr_classobj_new(const ScrClassObj *template, size_t ncaps);
|
|
void scr_classobj_trace_v(void *c, ScrTraceVisit visit, void *ctx);
|
|
/* void*-signature RC adapters (container slots) — scr_object.c. */
|
|
void *scr_classobj_retain_v(void *c);
|
|
void scr_classobj_release_v(void *c);
|
|
/* `X.name` (+1 — a no-op retain on the interned immortal). */
|
|
ScrStr *scr_classobj_name(ScrClassObj *c);
|
|
struct ScrDyn *scr_dyn_class_prototype(struct ScrDyn *value, struct ScrDyn *base);
|
|
struct ScrDyn *scr_dyn_class_base_prototype(struct ScrDyn *constructor);
|
|
struct ScrDyn *scr_dyn_class_inherit(struct ScrDyn *constructor, struct ScrDyn *base);
|
|
void scr_dyn_class_super(struct ScrDyn *constructor, struct ScrDyn *receiver, struct ScrDyn *arguments);
|
|
struct ScrDyn *scr_dyn_assign_prototype(struct ScrDyn *target, struct ScrDyn *sources, struct ScrDyn *protected_keys);
|
|
struct ScrDyn *scr_dyn_bag_get(struct ScrDyn *bag, ScrStr *key, struct ScrDyn *receiver);
|
|
struct ScrDyn *scr_dyn_reflect_get(struct ScrDyn *target, struct ScrDyn *key, struct ScrDyn *receiver);
|
|
bool scr_dyn_reflect_set(struct ScrDyn *target, struct ScrDyn *key, struct ScrDyn *value, struct ScrDyn *receiver);
|
|
bool scr_dyn_reflect_define(struct ScrDyn *receiver, struct ScrDyn *key, struct ScrDyn *value);
|
|
bool scr_dyn_abstract_eq(const struct ScrDyn *left, const struct ScrDyn *right);
|
|
int scr_dyn_function_kind(const struct ScrDyn *value);
|
|
void scr_dyn_bag_set(struct ScrDyn *bag, ScrStr *key, struct ScrDyn *value, struct ScrDyn *receiver);
|
|
/* The keyed-write miss on a fixed-shape record: throws the catchable
|
|
* TypeError naming the key (JS would add the property — the documented
|
|
* monomorphic-struct divergence). scr_object.c. */
|
|
void scr_record_key_miss(ScrStr *k);
|
|
|
|
typedef struct ScrError {
|
|
size_t rc;
|
|
const ScrVt *vt;
|
|
ScrStr *name; /* "TypeError", or whatever the user assigned */
|
|
ScrStr *message; /* "" when constructed without one, like Node */
|
|
ScrStr *code; /* NULL = absent (Node: no `code` property); fs/exec
|
|
* throw sites stamp the errno name ("ENOENT"). Part of
|
|
* the layout prefix: the compiler's %Error class defs
|
|
* carry a matching third field, so user subclasses
|
|
* embed the slot and release it NULL-guarded. */
|
|
struct ScrDyn *error_cause; /* NULL = absent; dyn undefined = present */
|
|
bool cause_enumerable; /* assignment creates an enumerable own property */
|
|
bool message_present; /* super() leaves the inherited message property visible */
|
|
bool message_enumerable; /* assignment creates an enumerable own property */
|
|
bool name_present;
|
|
bool name_enumerable;
|
|
ScrStr *stack_frames; /* captured native source frames */
|
|
ScrStr *stack; /* lazily formatted, cached stack */
|
|
double system_errno; /* present when system_call is non-NULL or this is
|
|
* nonzero (zlib errors carry errno without syscall) */
|
|
ScrStr *system_call;
|
|
ScrStr *system_path;
|
|
ScrStr *system_dest;
|
|
/* Node's SystemError (internal/errors.js): the context message and errno
|
|
* name behind `info`; NULL for every other error. */
|
|
ScrStr *system_message;
|
|
ScrStr *system_code;
|
|
bool native_plain; /* a native binding's coded Error: "Name: message", no "[code]" */
|
|
bool errno_order; /* ErrnoException key order: errno, code, path, syscall */
|
|
} ScrError;
|
|
|
|
enum {
|
|
SCR_ERR_ERROR = 0,
|
|
SCR_ERR_TYPE = 1,
|
|
SCR_ERR_RANGE = 2,
|
|
SCR_ERR_SYNTAX = 3,
|
|
SCR_ERR_DOMEX = 4, /* DOMException — ScrDomException, the wider layout */
|
|
SCR_ERR_REFERENCE = 5,
|
|
SCR_ERR_EVAL = 6,
|
|
SCR_ERR_URI = 7,
|
|
SCR_ERR_AGGREGATE = 8, /* AggregateError — ScrAggregateError, the wider layout */
|
|
SCR_ERR_ASSERTION = 9, /* node:assert's AssertionError — the Error layout */
|
|
SCR_ERR_SUPPRESSED = 10, /* SuppressedError — the Error layout; error and suppressed are view properties */
|
|
SCR_ERR_COUNT = 11,
|
|
};
|
|
|
|
extern SCR_TL ScrVt scr_error_vts[SCR_ERR_COUNT]; /* indexed by SCR_ERR_*; main() stamps pre/post */
|
|
|
|
struct ScrDyn; /* full declaration below (the checked-dynamic tree section) */
|
|
|
|
/* DOMException: the ScrError prefix (identical member order — an upcast is
|
|
* a pointer reinterpret) plus the WebIDL slots. The extra slots are HIDDEN
|
|
* from the compiler's IR field list (user `extends DOMException` is fenced
|
|
* so no subclass layout ever overlaps them); reads go through the
|
|
* scr_domex_* accessors. */
|
|
typedef struct ScrDomException {
|
|
size_t rc;
|
|
const ScrVt *vt;
|
|
ScrStr *name; /* "Error" default, or the resolved WebIDL name */
|
|
ScrStr *message; /* "" when constructed without one */
|
|
ScrStr *code; /* the Node string-code slot (stays NULL here) */
|
|
struct ScrDyn *error_cause; /* shared ScrError prefix; unused by DOMException */
|
|
bool cause_enumerable;
|
|
bool message_present;
|
|
bool message_enumerable;
|
|
bool name_present;
|
|
bool name_enumerable;
|
|
ScrStr *stack_frames;
|
|
ScrStr *stack;
|
|
double system_errno;
|
|
ScrStr *system_call;
|
|
ScrStr *system_path;
|
|
ScrStr *system_dest;
|
|
ScrStr *system_message;
|
|
ScrStr *system_code;
|
|
bool native_plain;
|
|
bool errno_order;
|
|
double dom_code; /* the WebIDL legacy code (0 when the name is off-table) */
|
|
bool has_cause; /* the options form carried a `cause` member */
|
|
struct ScrDyn *cause; /* owned; NULL when has_cause is false */
|
|
} ScrDomException;
|
|
|
|
/* new DOMException(message?, nameOrOptions?) — both dyn args borrowed,
|
|
* NULL-tolerant (NULL = absent = the dyn undefined). Returns +1. Never
|
|
* throws (dyn ToString is total). Lives in scr_json.c (the args are dyn
|
|
* values); the dyn-free half below stays in scr_error.c so the error
|
|
* unit links without the checked-dynamic tree. */
|
|
ScrError *scr_domex_new(const struct ScrDyn *message, const struct ScrDyn *name_or_options);
|
|
/* scr_error.c's dyn-free DOMException half: the blank allocation the
|
|
* constructors fill, the WebIDL name→legacy-code table, and the cause
|
|
* teardown hook scr_json.c installs before any cause can exist. */
|
|
ScrError *scr_domex_alloc(void);
|
|
double scr_domex_code_of(const ScrStr *name);
|
|
void scr_domex_install_cause_drop(void (*fn)(void *obj));
|
|
double scr_domex_code(ScrError *e); /* borrowed receiver */
|
|
bool scr_domex_has_cause(ScrError *e); /* borrowed receiver */
|
|
struct ScrDyn *scr_domex_cause(ScrError *e); /* +1 (dyn undefined when absent) */
|
|
/* structuredClone of a DOMException: WebIDL serialization — name/message
|
|
* copy, the code re-derives, cause does not serialize. Borrowed receiver
|
|
* and options (validated; throws on bad options / non-empty transfer).
|
|
* +1, NULL after a throw. */
|
|
ScrError *scr_domex_clone(ScrError *e, const struct ScrDyn *options);
|
|
/* Throw a fresh DOMException through the exception cell (atob/btoa's
|
|
* InvalidCharacterError sites). Copies both C strings; the _str form
|
|
* takes ownership of the message. */
|
|
SCR_COLD void scr_throw_domex(const char *name, const char *message);
|
|
SCR_COLD void scr_throw_domex_str(const char *name, ScrStr *message);
|
|
|
|
/* AggregateError: the ScrError prefix plus the `errors` list (a dyn
|
|
* array; NULL until set). Like DOMException's slots it is HIDDEN from
|
|
* the compiler's IR field list (user `extends AggregateError` is fenced)
|
|
* and read through scr_aggregate_errors. The dyn half (construction from
|
|
* an iterable's elements, the list itself) lives in scr_json.c; the
|
|
* teardown drops the list through the hook it installs. */
|
|
typedef struct ScrAggregateError {
|
|
ScrError base;
|
|
struct ScrDyn *errors;
|
|
} ScrAggregateError;
|
|
ScrError *scr_aggregate_alloc(void);
|
|
void scr_aggregate_install_errors_drop(void (*fn)(void *obj));
|
|
/* new AggregateError(errors, message?, options?) runs in spec order:
|
|
* scr_aggregate_new installs the message and cause (both borrowed, dyn
|
|
* undefined when absent; +1, NULL with an exception pending when the
|
|
* message conversion throws), then the caller iterates `errors` and
|
|
* hands the list to scr_aggregate_set_errors. */
|
|
ScrError *scr_aggregate_new(const struct ScrDyn *message, const struct ScrDyn *options);
|
|
void scr_aggregate_set_errors(ScrError *e, struct ScrDyn *errors); /* both borrowed */
|
|
struct ScrDyn *scr_aggregate_errors(ScrError *e); /* +1 */
|
|
|
|
/* new SuppressedError(error, suppressed, message) (scr_json.c): the message
|
|
* as in scr_error_new_options, then `error` and `suppressed` as
|
|
* non-enumerable properties of the error's dyn view. All arguments are
|
|
* borrowed (dyn undefined when absent). Returns +1, or NULL with an
|
|
* exception pending when the message conversion throws. */
|
|
ScrError *scr_suppressed_new(const struct ScrDyn *error, const struct ScrDyn *suppressed,
|
|
const struct ScrDyn *message);
|
|
|
|
/* Switch runtime-side error allocation to collector-headered (called from
|
|
* main() when the compiler's cycle fixpoint marks the hierarchy). */
|
|
void scr_error_set_traced(void);
|
|
|
|
/* Allocate + initialize (name = the kind's builtin name, message retained
|
|
* from the borrowed argument; NULL means ""). Returns +1. */
|
|
SCR_COLD ScrError *scr_error_new(int kind, ScrStr *message);
|
|
/* Initialize the error prefix of an already-allocated (zeroed) object —
|
|
* the super(message) call of a compiled `extends Error` constructor. Both
|
|
* arguments are borrowed. */
|
|
void scr_error_init(void *obj, int kind, ScrStr *message);
|
|
/* ECMAScript constructor arguments: dyn undefined defaults the message
|
|
* to ""; an options object's present cause is retained. All args borrowed.
|
|
* Message conversion can throw; new returns NULL with an exception pending.
|
|
* Kept separate so runtime-only throw sites need not link scr_json.c. */
|
|
ScrError *scr_error_new_options(int kind, const struct ScrDyn *message, const struct ScrDyn *options);
|
|
void scr_error_init_options(void *obj, int kind, const struct ScrDyn *message, const struct ScrDyn *options);
|
|
void scr_error_install_cause_drop(void (*fn)(void *obj));
|
|
bool scr_error_has_cause(ScrError *e);
|
|
struct ScrDyn *scr_error_cause(ScrError *e); /* +1, undefined when absent */
|
|
void scr_error_define_cause(ScrError *e, struct ScrDyn *value);
|
|
ScrError *scr_error_abort(struct ScrDyn *reason /*borrowed, NULL = undefined*/); /* +1 */
|
|
extern SCR_TL ScrError *scr_event_error_payload;
|
|
void scr_error_set_cause(ScrError *e, struct ScrDyn *value); /* borrowed operands */
|
|
void scr_error_delete_cause(ScrError *e);
|
|
/* ECMA Error.prototype.toString: "", name, message, or "name: message".
|
|
* Borrows e, returns +1. */
|
|
ScrStr *scr_error_to_string(ScrError *e);
|
|
void scr_error_install_message_reader(ScrStr *(*reader)(ScrError *));
|
|
void scr_error_install_name_reader(ScrStr *(*reader)(ScrError *));
|
|
ScrStr *scr_error_default_name(const ScrError *e);
|
|
ScrStr *scr_error_stack(ScrError *e);
|
|
|
|
ScrError *scr_error_retain(ScrError *e);
|
|
void scr_error_release(ScrError *e); /* dispatches through e->vt */
|
|
void *scr_error_retain_v(void *e);
|
|
void scr_error_release_v(void *e);
|
|
void scr_error_trace(void *e, ScrTraceVisit visit, void *ctx); /* no headered children */
|
|
ScrTraceFn scr_error_trace_arg(void); /* &scr_error_trace when traced, else NULL */
|
|
|
|
/* True when a thrown hierarchy object (SCR_EXC_OBJ payload) is an Error —
|
|
* its vtable's preorder lies inside Error's stamped interval. */
|
|
bool scr_error_is(const void *obj);
|
|
|
|
/* Throw a fresh builtin error through the exception cell. scr_throw_error
|
|
* takes ownership of message; the _msg form copies the C string. The _named
|
|
* form takes ownership of both strings and picks the
|
|
* builtin vtable whose class name matches `name` (Error otherwise). */
|
|
SCR_COLD void scr_throw_error(int kind, ScrStr *message);
|
|
SCR_COLD void scr_throw_error_msg(int kind, const char *message, size_t len);
|
|
SCR_COLD void scr_throw_error_named(ScrStr *name, ScrStr *message);
|
|
/* A read of a declare-d const nothing defines: throws Node's catchable
|
|
* ReferenceError "<name> is not defined". Borrows name; always throws. */
|
|
SCR_COLD void scr_undef_global_read(ScrStr *name);
|
|
/* The `code` slot (NodeJS.ErrnoException's .code): set stamps a fresh
|
|
* string from the C literal (replacing any previous value); get answers
|
|
* +1 or NULL when absent (the compiler's undefined arm). The _msg_code
|
|
* thrower is scr_throw_error_msg with the code stamped on the payload. */
|
|
void scr_error_set_code(ScrError *e, const char *code);
|
|
const char *scr_error_message_code(const ScrStr *msg);
|
|
ScrStr *scr_error_code(ScrError *e);
|
|
SCR_COLD void scr_throw_error_msg_code(int kind, const char *message, size_t len, const char *code);
|
|
/* The compiler-resolved Node-parity throw (error.nodeThrow): builtin
|
|
* error of `kind`, `code` stamped when non-empty. Borrows both. */
|
|
SCR_COLD void scr_throw_node_coded(double kind, const ScrStr *code, const ScrStr *msg);
|
|
|
|
#endif /* SCR_ERROR_H */
|