Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
- Deduplicate replay boundary refusals and derive filesystem error taping from the canonical tape-kind table.
12 changes: 6 additions & 6 deletions src/builtins.c
Original file line number Diff line number Diff line change
Expand Up @@ -2460,11 +2460,11 @@ Value* builtin_env_get(Value *arg) {
* Wrapping these with TRACE_NONDET_TAKE is not enough — the value the
* tape carries does not pin down the host state — so the contract is
* "fail loudly" at the boundary. Documented in docs/TRACE.md. */
int replay_blocks(const char *fn) {
int replay_blocks(const char *fn, const char *boundary) {
if (__builtin_expect(g_replay_enabled, 0)) {
rt_error(EK_IO, 0,
"%s: not replayable under EIGS_REPLAY (subprocess/concurrency "
"boundary; see docs/TRACE.md)", fn);
"%s: not replayable under EIGS_REPLAY (%s boundary; "
"see docs/TRACE.md)", fn, boundary);
return 1;
}
return 0;
Expand Down Expand Up @@ -4763,7 +4763,7 @@ Value* builtin_recv(Value *arg) {
* replay (test_builtin_errors BE07 under EIGS_REPLAY). */
Channel *ch = channel_arg(arg, "recv");
if (!ch) return make_null();
if (replay_blocks("recv")) return make_null();
if (replay_blocks("recv", "subprocess/concurrency")) return make_null();
pthread_mutex_lock(&ch->mutex);
while (ch->count == 0 && !ch->closed &&
!eigs_state_exit_requested(eigs_current_state(), NULL))
Expand All @@ -4783,7 +4783,7 @@ Value* builtin_recv(Value *arg) {
Value* builtin_try_recv(Value *arg) {
Channel *ch = channel_arg(arg, "try_recv"); /* #1072: validate before the replay block */
if (!ch) return make_null();
if (replay_blocks("try_recv")) return make_null();
if (replay_blocks("try_recv", "subprocess/concurrency")) return make_null();
pthread_mutex_lock(&ch->mutex);
Value *val = NULL;
if (ch->count > 0) {
Expand All @@ -4808,7 +4808,7 @@ Value* builtin_recv_timeout(Value *arg) {
}
Channel *ch = channel_arg(BUILTIN_LIST_VIEW(arg, 0), "recv_timeout");
if (!ch) return make_null();
if (replay_blocks("recv_timeout")) return make_null(); /* #1072: after validation */
if (replay_blocks("recv_timeout", "subprocess/concurrency")) return make_null(); /* #1072: after validation */
Value *ms_v = BUILTIN_LIST_VIEW(arg, 1);
if (ms_v->type != VAL_NUM) {
rt_error(EK_TYPE, 0, "recv_timeout: ms must be a number");
Expand Down
10 changes: 3 additions & 7 deletions src/builtins_host.c
Original file line number Diff line number Diff line change
Expand Up @@ -1123,11 +1123,7 @@ Value* builtin_remove_file(Value *arg) {
* one-string list for readers/mkdir, and [message, null] for ls (whose normal
* result may itself be a one-string list). */
static Value *file_io_error_message(const char *fn, const char *message) {
int taped = strcmp(fn, "mkdir") == 0 || strcmp(fn, "ls") == 0 ||
strcmp(fn, "write_text") == 0 ||
strcmp(fn, "write_bytes") == 0 ||
strcmp(fn, "remove_file") == 0 ||
strcmp(fn, "rename") == 0 || strncmp(fn, "read_", 5) == 0;
int taped = trace_core_kind_declared(fn);
if (taped && __builtin_expect(g_trace_enabled, 0)) {
Value *record = make_list(strcmp(fn, "ls") == 0 ? 2 : 1);
list_append_owned(record, make_str(message));
Expand Down Expand Up @@ -1615,7 +1611,7 @@ Value* builtin_exec_capture(Value *arg) {
* layer and [-1, ""] is exec_capture's documented "did not run". #1072:
* after the argument guard, so a wrong-typed call errs the same way in
* both modes. */
if (replay_blocks("exec_capture")) return exec_capture_result(-1, "");
if (replay_blocks("exec_capture", "subprocess/concurrency")) return exec_capture_result(-1, "");

/* Detect timeout form: [["cmd", ...], timeout_num] */
double timeout_sec = -1;
Expand Down Expand Up @@ -1766,7 +1762,7 @@ Value* builtin_exec_capture(Value *arg) {
* NUL-truncation), null on EOF. Same 10 MB cap as proc_read. */
Value* builtin_proc_read_buf(Value *arg) {
STRICT_LIST_MAX(arg, 2, "proc_read_buf");
if (replay_blocks("proc_read_buf")) return make_null();
if (replay_blocks("proc_read_buf", "subprocess/concurrency")) return make_null();
if (!arg || arg->type != VAL_LIST || arg->data.list.count != 2)
return make_null();
EIGS_VIEW(fd_v_view);
Expand Down
2 changes: 1 addition & 1 deletion src/builtins_internal.h
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ void register_host_builtins(Env *env);

/* builtins.c — shared with builtins_host.c's subprocess builtins: the #148
* "fail loudly under EIGS_REPLAY" boundary check (channels use it too). */
int replay_blocks(const char *fn);
int replay_blocks(const char *fn, const char *boundary);

/* builtins.c — the "is this name the language's?" predicate shared with
* builtins_host.c (build_corpus skips registered builtins). */
Expand Down
16 changes: 5 additions & 11 deletions src/ext_db.c
Original file line number Diff line number Diff line change
Expand Up @@ -4,20 +4,14 @@
*/

#include "ext_db_internal.h"
#include "builtins_internal.h"
#include "ext_names.h"
#include "trace.h"
#if EIGENSCRIPT_EXT_DB


#define DB_MAX_PARAMS 16

static int db_replay_blocks(const char *fn) {
if (!__builtin_expect(g_replay_enabled, 0)) return 0;
rt_error(EK_IO, 0, "%s: not replayable under EIGS_REPLAY "
"(database boundary; see docs/TRACE.md)", fn);
return 1;
}

/* Postgres's boolean type OID (pinned by the wire protocol; see the
* DB_OID_* block below, which carries the same value). */
#define DB_PARAM_OID_BOOL 16
Expand Down Expand Up @@ -88,7 +82,7 @@ static PGresult* db_exec_from_arg(Value *arg) {

Value* builtin_db_connect(Value *arg) {
(void)arg;
if (db_replay_blocks("db_connect")) return make_null();
if (replay_blocks("db_connect", "database")) return make_null();
const char *url = getenv("DATABASE_URL");
if (!url || !url[0]) {
return make_str("{\"status\": \"no_database\", \"message\": \"DATABASE_URL not set\"}");
Expand Down Expand Up @@ -274,7 +268,7 @@ static int db_emit_json_value(strbuf *sb, unsigned int oid, int isnull,

/* ---- DB query builtins ---- */
Value* builtin_db_query_value(Value *arg) {
if (db_replay_blocks("db_query_value")) return make_null();
if (replay_blocks("db_query_value", "database")) return make_null();
/* Run a SQL query and return row 0 col 0, typed by its SQL type (#887).
* Usage: db_query_value of "SELECT COUNT(*) FROM table"
* or: db_query_value of ["SELECT name FROM t WHERE id=$1", id]
Expand Down Expand Up @@ -328,7 +322,7 @@ Value* builtin_db_query_value(Value *arg) {
}

Value* builtin_db_execute(Value *arg) {
if (db_replay_blocks("db_execute")) return make_null();
if (replay_blocks("db_execute", "database")) return make_null();
/* Run a SQL command with optional params. Returns "ok"; raises on failure.
* Usage: db_execute of "INSERT INTO ..." (no params)
* or: db_execute of ["INSERT INTO t (a) VALUES ($1)", param1] */
Expand All @@ -348,7 +342,7 @@ Value* builtin_db_execute(Value *arg) {
}

Value* builtin_db_query_json(Value *arg) {
if (db_replay_blocks("db_query_json")) return make_null();
if (replay_blocks("db_query_json", "database")) return make_null();
/* Run SQL and return all rows as a JSON array of objects, each value
* carrying its SQL type (#887).
* Usage: db_query_json of "SELECT id, name FROM table"
Expand Down
31 changes: 11 additions & 20 deletions src/ext_store.c
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
*/

#include "ext_store_internal.h"
#include "builtins_internal.h"
#include "trace.h"

#if EIGENSCRIPT_FREESTANDING
Expand Down Expand Up @@ -864,16 +865,6 @@ static int page_data_used(Page *page) {
/* A store handle names live file state, and writes affect a world that the
* trace tape does not reconstruct. Refuse the whole family rather than
* recording a handle id or allowing any operation to consult live storage. */
static int store_replay_blocks(const char *fn) {
if (__builtin_expect(g_replay_enabled, 0)) {
rt_error(EK_IO, 0,
"%s: not replayable under EIGS_REPLAY (store boundary; "
"see docs/TRACE.md)", fn);
return 1;
}
return 0;
}

/* Free a Store and its one owned Value (the parsed catalog dict). Every
* free(store) must go through here — the catalog is a make_dict / decoded-JSON
* Value that leaks otherwise (test_store/lab/handle_forge). */
Expand Down Expand Up @@ -902,7 +893,7 @@ static Value* builtin_store_open(Value *arg) {
rt_error(EK_TYPE, 0, "store_open requires a string path\n");
return make_null();
}
if (store_replay_blocks("store_open")) return make_null();
if (replay_blocks("store_open", "store")) return make_null();
const char *path = arg->data.str;

Store *store = xcalloc(1, sizeof(Store));
Expand Down Expand Up @@ -974,7 +965,7 @@ static Value* builtin_store_open(Value *arg) {
static Value* builtin_store_close(Value *arg) {
Store *store = store_arg(arg, "store_close");
if (!store) return make_null();
if (store_replay_blocks("store_close")) return make_null();
if (replay_blocks("store_close", "store")) return make_null();
/* Release handle BEFORE freeing memory to prevent use-after-free
* if another thread calls get_store() concurrently. */
Value *id_val = (arg && arg->type == VAL_DICT) ? store_dict_get_view(arg, "_store_id", &(EigsView){{0}}) : NULL;
Expand Down Expand Up @@ -1003,7 +994,7 @@ static Value* builtin_store_put(Value *arg) {
}
Store *store = store_arg(list_get_view(arg, 0, &(EigsView){{0}}), "store_put");
if (!store) return make_null();
if (store_replay_blocks("store_put")) return make_null();
if (replay_blocks("store_put", "store")) return make_null();
Value *col_val = list_get_view(arg, 1, &(EigsView){{0}});
Value *record = list_get_view(arg, 2, &(EigsView){{0}});
if (!col_val || col_val->type != VAL_STR) {
Expand Down Expand Up @@ -1168,7 +1159,7 @@ static Value* builtin_store_get(Value *arg) {
}
Store *store = store_arg(list_get_view(arg, 0, &(EigsView){{0}}), "store_get");
if (!store) return make_null();
if (store_replay_blocks("store_get")) return make_null();
if (replay_blocks("store_get", "store")) return make_null();
Value *col_val = list_get_view(arg, 1, &(EigsView){{0}});
Value *key_val = list_get_view(arg, 2, &(EigsView){{0}});
if (!col_val || col_val->type != VAL_STR) return make_null();
Expand Down Expand Up @@ -1331,7 +1322,7 @@ static Value* builtin_store_delete(Value *arg) {
* than unwinding, so this return is the post-raise placeholder, not
* "deleted nothing". Same shape as the arity guard above. */
if (!store) return make_bool(0);
if (store_replay_blocks("store_delete")) return make_bool(0);
if (replay_blocks("store_delete", "store")) return make_bool(0);
Value *col_val = list_get_view(arg, 1, &(EigsView){{0}});
Value *key_val = list_get_view(arg, 2, &(EigsView){{0}});
ARG_GUARD(!col_val || col_val->type != VAL_STR,
Expand Down Expand Up @@ -1380,7 +1371,7 @@ static Value* builtin_store_query(Value *arg) {
}
Store *store = store_arg(list_get_view(arg, 0, &(EigsView){{0}}), "store_query");
if (!store) return make_list(0);
if (store_replay_blocks("store_query")) return make_list(0);
if (replay_blocks("store_query", "store")) return make_list(0);
Value *col_val = list_get_view(arg, 1, &(EigsView){{0}});
if (!col_val || col_val->type != VAL_STR) return make_list(0);

Expand Down Expand Up @@ -1433,7 +1424,7 @@ static Value* builtin_store_count(Value *arg) {
* count of 0. Distinct from the two documented-answer zeros below, which
* are store_count's real result for an unknown collection. */
if (!store) return make_num(0);
if (store_replay_blocks("store_count")) return make_num(0);
if (replay_blocks("store_count", "store")) return make_num(0);
Value *col_val = list_get_view(arg, 1, &(EigsView){{0}});
ARG_GUARD(!col_val || col_val->type != VAL_STR,
"store_count", "[store handle, string collection]", make_num(0));
Expand Down Expand Up @@ -1488,7 +1479,7 @@ static Value* builtin_store_update(Value *arg) {
* than unwinding, so this return is the post-raise placeholder, not
* "no record updated". */
if (!store) return make_bool(0);
if (store_replay_blocks("store_update")) return make_bool(0);
if (replay_blocks("store_update", "store")) return make_bool(0);
ARG_GUARD(!col_val || col_val->type != VAL_STR,
"store_update", "[store handle, string collection, key, record]",
make_bool(0));
Expand Down Expand Up @@ -1601,7 +1592,7 @@ static Value* builtin_store_update(Value *arg) {
static Value* builtin_store_collections(Value *arg) {
Store *store = store_arg(arg, "store_collections");
if (!store) return make_list(0);
if (store_replay_blocks("store_collections")) return make_list(0);
if (replay_blocks("store_collections", "store")) return make_list(0);
Value *list = make_list(store->catalog->data.dict.count);
for (int i = 0; i < store->catalog->data.dict.count; i++) {
list_append_owned(list, make_str(store->catalog->data.dict.keys[i]));
Expand All @@ -1626,7 +1617,7 @@ static Value* builtin_store_drop(Value *arg) {
* store_drop's documented "no" (that is the documented-answer zero
* below). */
if (!store) return make_bool(0);
if (store_replay_blocks("store_drop")) return make_bool(0);
if (replay_blocks("store_drop", "store")) return make_bool(0);
Value *col_val = list_get_view(arg, 1, &(EigsView){{0}});
ARG_GUARD(!col_val || col_val->type != VAL_STR,
"store_drop", "[store handle, string collection]", make_bool(0));
Expand Down
4 changes: 4 additions & 0 deletions src/trace.c
Original file line number Diff line number Diff line change
Expand Up @@ -1996,6 +1996,10 @@ static unsigned core_tape_kinds(const char *fn) {
return 0;
}

int trace_core_kind_declared(const char *name) {
return name && core_tape_kinds(name) != 0;
}

int trace_declare_kind(const char *name, unsigned kinds) {
if (!name || !*name || !kinds || core_tape_kinds(name)) return 0;
pthread_mutex_lock(&g_host_kinds_mu);
Expand Down
2 changes: 2 additions & 0 deletions src/trace.h
Original file line number Diff line number Diff line change
Expand Up @@ -448,6 +448,8 @@ int trace_replay_refuse_off_owner(const char *fn);
/* #1637: declare the return kinds (a ValType bit set, 1u << VAL_X) of a
* host-recorded nondet name; replay refuses a record of another kind, and a
* name declared nowhere. 0 = refused (empty name, no kinds, a core name). */
/* True when a runtime builtin has a return-kind row in k_tape_kinds. */
int trace_core_kind_declared(const char *name);
int trace_declare_kind(const char *name, unsigned kinds);

#define TRACE_NONDET_RET(name, expr) do { \
Expand Down
16 changes: 8 additions & 8 deletions tools/strict_shape_contracts.json
Original file line number Diff line number Diff line change
Expand Up @@ -628,11 +628,11 @@
],
"disposition": "exempt",
"body_sha256": [
"2a0585cb8bc27e8913bc2a4934e967620b36e00f161615aaee7dd370b8e93a27"
"6b2fd24e37e2c8815cc09b9c56984e8b2393f6af707ff41170c97457f39a4f9f"
],
"source_sha256": {
"builtin_db_execute": [
"2a0585cb8bc27e8913bc2a4934e967620b36e00f161615aaee7dd370b8e93a27"
"6b2fd24e37e2c8815cc09b9c56984e8b2393f6af707ff41170c97457f39a4f9f"
],
"db_build_query": [
"43adc9e73c43cc8806b029b7b9abb68141f106ac27ef0627d768c65e5d407c21"
Expand All @@ -654,11 +654,11 @@
],
"disposition": "exempt",
"body_sha256": [
"86243406977be526aae70eab442f2adcfc7f00f7a2547d0ed926f1e7613e6c5d"
"342be3ccd930d86dd44f9c06119e7a9fb7ea7abfc4626ef0bfd19aa97502a30a"
],
"source_sha256": {
"builtin_db_query_json": [
"86243406977be526aae70eab442f2adcfc7f00f7a2547d0ed926f1e7613e6c5d"
"342be3ccd930d86dd44f9c06119e7a9fb7ea7abfc4626ef0bfd19aa97502a30a"
],
"db_build_query": [
"43adc9e73c43cc8806b029b7b9abb68141f106ac27ef0627d768c65e5d407c21"
Expand All @@ -680,11 +680,11 @@
],
"disposition": "exempt",
"body_sha256": [
"231665e1df1d4fd381dddeea01664035fd7f998fc4c54a8284c73f93ce789a13"
"294571ce18bb52316eda6dee7bd6da02e85882c53238c915a8b57dd6f88c0dd4"
],
"source_sha256": {
"builtin_db_query_value": [
"231665e1df1d4fd381dddeea01664035fd7f998fc4c54a8284c73f93ce789a13"
"294571ce18bb52316eda6dee7bd6da02e85882c53238c915a8b57dd6f88c0dd4"
],
"db_build_query": [
"43adc9e73c43cc8806b029b7b9abb68141f106ac27ef0627d768c65e5d407c21"
Expand Down Expand Up @@ -790,11 +790,11 @@
],
"disposition": "exempt",
"body_sha256": [
"167f0efe94c146d6daa77ed91fd3ef73368fcfacfd373a2ee91ba6f5bccc9e0d"
"634c385bd84ab175fbb2c93000dfa9ffda1f899d6f4cb8242bcd164feb219985"
],
"source_sha256": {
"builtin_exec_capture": [
"167f0efe94c146d6daa77ed91fd3ef73368fcfacfd373a2ee91ba6f5bccc9e0d"
"634c385bd84ab175fbb2c93000dfa9ffda1f899d6f4cb8242bcd164feb219985"
],
"eigs_list_num": [
"25755c9b1f519fb0447e2d648dc0272419914db805f9249954cd2e146b48a3bc"
Expand Down
8 changes: 7 additions & 1 deletion tools/tape_kinds_check.sh
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@
# replay, on a user's tape. This gate fails first, at build review:
# taped = every name src/*.c records or takes: TRACE_NONDET_TAKE/RET/
# RECORD("x"), ARG_GUARD_TAPED/PRETAKE(..., "x", ...),
# trace_replay_take("x"), trace_nondet_value("x"), and
# trace_replay_take("x"), trace_nondet_value("x"),
# file_io_error("x", ...) (all filesystem errors are taped), and
# trace_source_record/replay("x") (which also emits `x_chunk`)
# declared = every {"x", ...} row of k_tape_kinds
# and requires taped == declared, both non-empty. The smoke/embedding test
Expand All @@ -30,6 +31,11 @@ for f in "$SRC_DIR"/*.c; do
print m
s = substr(s, RSTART + RLENGTH)
}
while (match(s, /file_io_error[(]"[a-z_0-9]+"/)) {
m = substr(s, RSTART, RLENGTH); sub(/^[^"]*"/, "", m); sub(/"$/, "", m)
print m
s = substr(s, RSTART + RLENGTH)
}
while (match(s, /trace_source_(record|replay)[(]"[a-z_0-9]+"/)) {
m = substr(s, RSTART, RLENGTH); sub(/^[^"]*"/, "", m); sub(/"$/, "", m)
print m; print m "_chunk"
Expand Down
Loading