commit 8b8690a117ea8e74ec13bbd64458f29402317ed1
parent 70e13d667703d79d7c61f6d01569038e9e0e88d4
Author: Ryan Sepassi <rsepassi@gmail.com>
Date: Fri, 19 Jun 2026 10:49:03 -0700
Validate deepset source and glob consistency
Diffstat:
| M | src/build/resolve.c | | | 197 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
1 file changed, 197 insertions(+), 0 deletions(-)
diff --git a/src/build/resolve.c b/src/build/resolve.c
@@ -3,6 +3,9 @@
#include "remote.h"
#include "runner.h"
+#undef KIT_TRACE_MODULE
+#define KIT_TRACE_MODULE "build/resolve"
+
#include <stdio.h>
#include <string.h>
@@ -58,6 +61,168 @@ static int config_observation_match(const BuildConfig* cfg,
return build_id_eq(value_hash, obs->value_hash);
}
+typedef struct BuildSeenId {
+ uint8_t id[BUILD_HASH_LEN];
+} BuildSeenId;
+
+typedef struct BuildLeafsetCheck {
+ KitHeap* heap;
+ BuildSourceLeaf* sources;
+ size_t n_sources;
+ size_t cap_sources;
+ BuildGlobLeaf* globs;
+ size_t n_globs;
+ size_t cap_globs;
+ BuildSeenId* visited;
+ size_t n_visited;
+ size_t cap_visited;
+ char* err;
+ size_t errcap;
+} BuildLeafsetCheck;
+
+static int leafset_check_err(BuildLeafsetCheck* ck, const char* what,
+ const char* key) {
+ if (ck && ck->err && ck->errcap) {
+ if (key)
+ snprintf(ck->err, ck->errcap, "%s: %s", what, key);
+ else
+ snprintf(ck->err, ck->errcap, "%s", what);
+ }
+ return BUILD_ERR;
+}
+
+static int leafset_check_grow(BuildLeafsetCheck* ck, void** ptr, size_t* cap,
+ size_t want, size_t elem_size,
+ size_t elem_align) {
+ size_t newcap, old_size, new_size;
+ void* fresh;
+ if (!ck || !ck->heap || !ptr || !cap || elem_size == 0u) return BUILD_ERR;
+ if (*cap >= want) return BUILD_OK;
+ newcap = *cap ? *cap : 16u;
+ while (newcap < want) {
+ if (newcap > ((size_t)-1) / 2u)
+ return leafset_check_err(ck, "deepset consistency table too large", NULL);
+ newcap *= 2u;
+ }
+ if (newcap > ((size_t)-1) / elem_size)
+ return leafset_check_err(ck, "deepset consistency table too large", NULL);
+ old_size = *cap * elem_size;
+ new_size = newcap * elem_size;
+ fresh = ck->heap->realloc(ck->heap, *ptr, old_size, new_size, elem_align);
+ if (!fresh) return leafset_check_err(ck, "out of memory", NULL);
+ *ptr = fresh;
+ *cap = newcap;
+ return BUILD_OK;
+}
+
+static int leafset_check_source(BuildLeafsetCheck* ck,
+ const BuildSourceLeaf* row) {
+ size_t i;
+ if (!ck || !row) return BUILD_ERR;
+ for (i = 0; i < ck->n_sources; ++i) {
+ BuildSourceLeaf* seen = &ck->sources[i];
+ if (strcmp(seen->path, row->path) != 0) continue;
+ if (seen->absent != row->absent)
+ return leafset_check_err(ck, "inconsistent source observation",
+ row->path);
+ if (!row->absent && !build_id_eq(seen->blob, row->blob))
+ return leafset_check_err(ck, "inconsistent source observation",
+ row->path);
+ return BUILD_OK;
+ }
+ if (leafset_check_grow(ck, (void**)&ck->sources, &ck->cap_sources,
+ ck->n_sources + 1u, sizeof ck->sources[0],
+ _Alignof(BuildSourceLeaf)) != BUILD_OK)
+ return BUILD_ERR;
+ ck->sources[ck->n_sources++] = *row;
+ return BUILD_OK;
+}
+
+static int leafset_check_glob(BuildLeafsetCheck* ck, const BuildGlobLeaf* row) {
+ size_t i;
+ if (!ck || !row) return BUILD_ERR;
+ for (i = 0; i < ck->n_globs; ++i) {
+ BuildGlobLeaf* seen = &ck->globs[i];
+ if (strcmp(seen->pattern, row->pattern) != 0) continue;
+ if (!build_id_eq(seen->result_hash, row->result_hash))
+ return leafset_check_err(ck, "inconsistent glob observation",
+ row->pattern);
+ return BUILD_OK;
+ }
+ if (leafset_check_grow(ck, (void**)&ck->globs, &ck->cap_globs,
+ ck->n_globs + 1u, sizeof ck->globs[0],
+ _Alignof(BuildGlobLeaf)) != BUILD_OK)
+ return BUILD_ERR;
+ ck->globs[ck->n_globs++] = *row;
+ return BUILD_OK;
+}
+
+static int leafset_check_mark_visited(BuildLeafsetCheck* ck,
+ const uint8_t id[BUILD_HASH_LEN],
+ int* already_seen) {
+ size_t i;
+ if (!ck || !id || !already_seen) return BUILD_ERR;
+ *already_seen = 0;
+ for (i = 0; i < ck->n_visited; ++i) {
+ if (build_id_eq(ck->visited[i].id, id)) {
+ *already_seen = 1;
+ return BUILD_OK;
+ }
+ }
+ if (leafset_check_grow(ck, (void**)&ck->visited, &ck->cap_visited,
+ ck->n_visited + 1u, sizeof ck->visited[0],
+ _Alignof(BuildSeenId)) != BUILD_OK)
+ return BUILD_ERR;
+ memcpy(ck->visited[ck->n_visited++].id, id, BUILD_HASH_LEN);
+ return BUILD_OK;
+}
+
+static int leafset_check_walk(BuildLeafsetCheck* ck, const BuildLeafSet* node,
+ int mark_self) {
+ size_t i;
+ if (!ck || !node) return BUILD_ERR;
+ if (mark_self) {
+ int already_seen = 0;
+ if (leafset_check_mark_visited(ck, node->id, &already_seen) != BUILD_OK)
+ return BUILD_ERR;
+ if (already_seen) return BUILD_OK;
+ }
+ for (i = 0; i < node->n_sources; ++i) {
+ if (leafset_check_source(ck, &node->sources[i]) != BUILD_OK)
+ return BUILD_ERR;
+ }
+ for (i = 0; i < node->n_globs; ++i) {
+ if (leafset_check_glob(ck, &node->globs[i]) != BUILD_OK)
+ return BUILD_ERR;
+ }
+ for (i = 0; i < node->n_children; ++i) {
+ if (leafset_check_walk(ck, node->children[i], 1) != BUILD_OK)
+ return BUILD_ERR;
+ }
+ return BUILD_OK;
+}
+
+static int build_leafset_check_consistent(KitBuildCoordinator* c,
+ const BuildLeafSet* root,
+ int root_has_id, char* err,
+ size_t errcap) {
+ BuildLeafsetCheck ck;
+ int ok;
+ if (!c || !root) return BUILD_ERR;
+ memset(&ck, 0, sizeof ck);
+ ck.heap = c->ctx->heap;
+ ck.err = err;
+ ck.errcap = errcap;
+ ok = leafset_check_walk(&ck, root, root_has_id);
+ if (ck.sources)
+ ck.heap->free(ck.heap, ck.sources, ck.cap_sources * sizeof ck.sources[0]);
+ if (ck.globs)
+ ck.heap->free(ck.heap, ck.globs, ck.cap_globs * sizeof ck.globs[0]);
+ if (ck.visited)
+ ck.heap->free(ck.heap, ck.visited, ck.cap_visited * sizeof ck.visited[0]);
+ return ok;
+}
+
static int shallow_direct_match(KitBuildCoordinator* c,
const BuildShallowTrace* st,
const BuildConfig* cfg,
@@ -417,11 +582,14 @@ scan_record:
BUILD_OK)
continue;
memset(&dt, 0, sizeof dt);
+ err[0] = '\0';
if (build_deep_parse(fd.data, fd.size, &dt, NULL, 0) == BUILD_OK &&
strlen(dt.target) == target.len &&
memcmp(dt.target, target.s, target.len) == 0 &&
build_id_eq(dt.argv, argv_id) &&
build_coord_deepset_load(c, dt.deepset, &leaf) == BUILD_OK &&
+ build_leafset_check_consistent(c, leaf, 1, err, sizeof err) ==
+ BUILD_OK &&
build_leafset_refresh(c, leaf, cfg, &match) == BUILD_OK && match &&
build_materialize(c, dt.output, out->path, sizeof out->path) ==
BUILD_OK) {
@@ -436,6 +604,9 @@ scan_record:
build_coord_stat_bump(c, BUILD_STAT_DEEP_HIT);
return BUILD_OK;
}
+ if (err[0])
+ KIT_LOGD("skip deep trace for %.*s: %s", KIT_SLICE_ARG(target), err);
+ err[0] = '\0';
build_store_release(&c->store, &fd);
}
for (i = 0; i < rec.n_rows; ++i) {
@@ -536,11 +707,14 @@ scan_record:
BUILD_OK)
continue;
memset(&dt, 0, sizeof dt);
+ err[0] = '\0';
if (build_test_deep_parse(fd.data, fd.size, &dt, NULL, 0) == BUILD_OK &&
strlen(dt.target) == target.len &&
memcmp(dt.target, target.s, target.len) == 0 &&
build_id_eq(dt.argv, argv_id) &&
build_coord_deepset_load(c, dt.deepset, &leaf) == BUILD_OK &&
+ build_leafset_check_consistent(c, leaf, 1, err, sizeof err) ==
+ BUILD_OK &&
build_leafset_refresh(c, leaf, cfg, &match) == BUILD_OK && match &&
build_materialize(c, dt.output, out->path, sizeof out->path) ==
BUILD_OK) {
@@ -558,6 +732,10 @@ scan_record:
build_coord_stat_bump(c, BUILD_STAT_TEST_CACHE_HIT);
return BUILD_OK;
}
+ if (err[0])
+ KIT_LOGD("skip test deep trace for %.*s: %s", KIT_SLICE_ARG(target),
+ err);
+ err[0] = '\0';
build_store_release(&c->store, &fd);
}
for (i = 0; i < rec.n_rows; ++i) {
@@ -647,6 +825,7 @@ int build_leafset_union(KitBuildCoordinator* c, const BuildLeafSet* direct,
const BuildLeafSet** out) {
BuildDeepSet ds;
BuildLeafSet leaf;
+ BuildLeafSet check_root;
uint8_t(*child_ids)[BUILD_HASH_LEN] = NULL;
const BuildLeafSet** child_ptrs = NULL;
KitWriter* w = NULL;
@@ -670,6 +849,24 @@ int build_leafset_union(KitBuildCoordinator* c, const BuildLeafSet* direct,
child_ptrs[i] = children[i];
}
}
+ memset(&check_root, 0, sizeof check_root);
+ snprintf(check_root.target, sizeof check_root.target, "%s", direct->target);
+ memcpy(check_root.recipe, direct->recipe, BUILD_HASH_LEN);
+ check_root.configs = direct->configs;
+ check_root.n_configs = direct->n_configs;
+ check_root.sources = direct->sources;
+ check_root.n_sources = direct->n_sources;
+ check_root.globs = direct->globs;
+ check_root.n_globs = direct->n_globs;
+ check_root.blobs = direct->blobs;
+ check_root.n_blobs = direct->n_blobs;
+ check_root.children = child_ptrs;
+ check_root.n_children = nchildren;
+ if (build_leafset_check_consistent(c, &check_root, 0, err, sizeof err) !=
+ BUILD_OK) {
+ build_diagf(c->ctx, "build: inconsistent deepset closure: %s", err);
+ goto out;
+ }
memset(&ds, 0, sizeof ds);
snprintf(ds.target, sizeof ds.target, "%s", direct->target);
memcpy(ds.recipe, direct->recipe, BUILD_HASH_LEN);