mirror of
https://github.com/DeusData/codebase-memory-mcp.git
synced 2026-11-04 10:48:20 +00:00
fix(store): a failed resolver stage keeps its SQL until the writer finalizes
The direct writer's tables point at CREATE text the stage owns, and finalize runs whether the stage worked or not. On failure the stage was freed first, so finalize built sqlite_master from freed memory: heap-use-after-free in importance and pipeline_semantic_manifest_repro, whose rejected publish reaches it. The caller already frees the stage on every path, so it now gets the stage back either way. Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
This commit is contained in:
1 parent
6b97b569db
commit
c263d9656d
3 files changed
+23
-25
No files matched your search
@@ -4176,10 +4176,9 @@ static bool resolver_stage_tables(cbm_db_writer_t *writer, void *context) {
|
||||
const cbm_resolve_file_t *files = cbm_gbuf_resolver_files(generation->gbuf, &file_count);
|
||||
const cbm_resolve_summary_t *summaries =
|
||||
cbm_gbuf_resolver_summaries(generation->gbuf, &summary_count);
|
||||
write->stage =
|
||||
cbm_store_resolver_stage_write(writer, generation->project, rows, count, files, file_count,
|
||||
summaries, summary_count, generation->workers);
|
||||
return write->stage != NULL;
|
||||
return cbm_store_resolver_stage_write(writer, generation->project, rows, count, files,
|
||||
file_count, summaries, summary_count, generation->workers,
|
||||
&write->stage);
|
||||
}
|
||||
|
||||
static int publish_generation_impl(const cbm_pipeline_generation_t *generation,
|
||||
|
||||
@@ -1674,7 +1674,9 @@ static bool evidence_run(sqlite3 *db, cbm_db_writer_t *direct, const char *proje
|
||||
cbm_free(CBM_MEM_CLASS_STORE, cache);
|
||||
cbm_arena_destroy(&w->owned);
|
||||
cbm_free(CBM_MEM_CLASS_STORE, writer);
|
||||
if (ok && out)
|
||||
/* The writer's tables keep pointing at the stage's SQL until finalize, which runs whether
|
||||
* this worked or not, so a caller who asked for the stage frees it either way. */
|
||||
if (out)
|
||||
*out = stage;
|
||||
else
|
||||
cbm_store_resolver_stage_free(stage);
|
||||
@@ -1687,17 +1689,14 @@ bool cbm_store_resolver_evidence(sqlite3 *db, const char *project, const cbm_res
|
||||
return db && evidence_run(db, NULL, project, rows, count, files, file_count, summaries,
|
||||
summary_count, commit_batches, workers, NULL);
|
||||
}
|
||||
cbm_resolver_stage_t *cbm_store_resolver_stage_write(cbm_db_writer_t *writer, const char *project,
|
||||
const cbm_resolve_record_t *rows, size_t count,
|
||||
const cbm_resolve_file_t *files,
|
||||
size_t file_count,
|
||||
const cbm_resolve_summary_t *summaries,
|
||||
size_t summary_count, int workers) {
|
||||
cbm_resolver_stage_t *stage = NULL;
|
||||
return writer && evidence_run(NULL, writer, project, rows, count, files, file_count, summaries,
|
||||
summary_count, false, workers, &stage)
|
||||
? stage
|
||||
: NULL;
|
||||
bool cbm_store_resolver_stage_write(cbm_db_writer_t *writer, const char *project,
|
||||
const cbm_resolve_record_t *rows, size_t count,
|
||||
const cbm_resolve_file_t *files, size_t file_count,
|
||||
const cbm_resolve_summary_t *summaries, size_t summary_count,
|
||||
int workers, cbm_resolver_stage_t **stage) {
|
||||
return writer && stage &&
|
||||
evidence_run(NULL, writer, project, rows, count, files, file_count, summaries,
|
||||
summary_count, false, workers, stage);
|
||||
}
|
||||
bool cbm_store_resolver_stage_finish(sqlite3 *db, const char *project,
|
||||
const cbm_resolver_stage_t *stage) {
|
||||
|
||||
@@ -15,16 +15,16 @@ bool cbm_store_resolver_evidence(sqlite3 *db, const char *project, const cbm_res
|
||||
/* The same write when the store is a new file still being built: facts, supports,
|
||||
* occurrences and their text go straight into writer's pages, tens of millions of rows
|
||||
* that SQL would take one at a time. The tables are ordinary and the views over them
|
||||
* read back the same values. NULL on failure, which also fails the file. What is
|
||||
* returned holds no fact and no digest, only what the store then inserts, and it
|
||||
* borrows the same rows as this call until freed. */
|
||||
* read back the same values. False on failure, which also fails the file. *stage holds
|
||||
* no fact and no digest, only what the store then inserts, and it borrows the same rows
|
||||
* as this call until freed. Free it after the writer's finalize, whatever this returned:
|
||||
* the writer's tables keep pointing at SQL it owns. */
|
||||
typedef struct cbm_resolver_stage cbm_resolver_stage_t;
|
||||
cbm_resolver_stage_t *cbm_store_resolver_stage_write(cbm_db_writer_t *writer, const char *project,
|
||||
const cbm_resolve_record_t *rows, size_t count,
|
||||
const cbm_resolve_file_t *files,
|
||||
size_t file_count,
|
||||
const cbm_resolve_summary_t *summaries,
|
||||
size_t summary_count, int workers);
|
||||
bool cbm_store_resolver_stage_write(cbm_db_writer_t *writer, const char *project,
|
||||
const cbm_resolve_record_t *rows, size_t count,
|
||||
const cbm_resolve_file_t *files, size_t file_count,
|
||||
const cbm_resolve_summary_t *summaries, size_t summary_count,
|
||||
int workers, cbm_resolver_stage_t **stage);
|
||||
/* The rest of the write, on the finished file. */
|
||||
bool cbm_store_resolver_stage_finish(sqlite3 *db, const char *project,
|
||||
const cbm_resolver_stage_t *stage);
|
||||
|
||||
Reference in new issue
Block a user