Files
scriptc/packages/runtime/src/scr_error.h
T

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 */