diff --git a/src/ops/agg_engine.c b/src/ops/agg_engine.c index 984faff6..f5b33887 100644 --- a/src/ops/agg_engine.c +++ b/src/ops/agg_engine.c @@ -5421,7 +5421,10 @@ ray_t* exec_group_v2(ray_graph_t* g, ray_op_t* op, ray_t* tbl, * groups themselves; the other routes trim their full result. */ ray_group_emit_filter_t ef = ray_group_emit_filter_active(); const ray_group_emit_filter_t* efp = ef.enabled ? &ef : NULL; - if (!g || !g->selection) + /* exec_group_v2_run reads g's op extensions unconditionally; a NULL + * graph is an error here, not a request for an unfiltered run. */ + if (!g) return ray_error("nyi", NULL); + if (!g->selection) return exec_group_v2_run(g, op, tbl, ray_table_nrows(tbl), NULL, NULL, 0, group_limit, efp); diff --git a/src/ops/exec.c b/src/ops/exec.c index daa2fe30..147412cf 100644 --- a/src/ops/exec.c +++ b/src/ops/exec.c @@ -4060,7 +4060,13 @@ ray_t* ray_execute(ray_graph_t* g, ray_op_t* root) { * would reset the elapsed clock and fire premature "final" ticks. */ ray_t* scan_err = validate_scan_columns(g); if (scan_err) return scan_err; - return ray_execute_inner(g, root); + /* Never NULL: callers test RAY_IS_ERR, which is false for NULL, and then + * dereference. The inner paths hand NULL back unchanged when a node, + * a compaction or a merge fails to allocate (the flat path's + * `return result`, the streaming path's seg_tbl / partial / merged + * returns); `(times ...)` would then crash in loop_count. */ + ray_t* result = ray_execute_inner(g, root); + return result ? result : ray_error("oom", NULL); } /* Flatten one parted/mapcommon column into a dense vector (mirrors the diff --git a/src/ops/idxop.c b/src/ops/idxop.c index cc9f9a36..05baa57f 100644 --- a/src/ops/idxop.c +++ b/src/ops/idxop.c @@ -745,11 +745,15 @@ static ray_t* prepare_attach_ex(ray_t** vp, const char* what, return ray_error("type", "%s: cannot index a slice; materialize first", what); if (v->attrs & RAY_ATTR_HAS_INDEX) { ray_index_drop(&v); + if (!v) return ray_error("oom", NULL); /* keep *vp intact */ if (RAY_IS_ERR(v)) return v; *vp = v; } v = ray_cow(v); - if (!v || RAY_IS_ERR(v)) return v; + /* Never hand back NULL: every caller tests only RAY_IS_ERR and then + * dereferences, so a failed copy must surface as an error. */ + if (!v) return ray_error("oom", NULL); + if (RAY_IS_ERR(v)) return v; *vp = v; /* Numeric vectors carry any index kind; STR carries only RAY_IDX_DICT * (codes live alongside the descriptors — the column representation is