mirror of
https://github.com/DeusData/codebase-memory-mcp.git
synced 2026-11-04 10:48:20 +00:00
fix: size the compress path with size_t and prune the cache from the walk
Two loose ends from the bounds work. The decompression side already took size_t so a >2 GiB capacity could not wrap through int, but compression still took int for both the source length and the destination capacity, and the artifact export cast a size_t database size down to reach it. A database past 2 GiB would have handed the encoder a negative length. Both lengths and the bound helper are size_t now, and the function returns int64_t like its decompressing counterpart. The discovery walk now prunes the cache directory by absolute path. A custom CBM_CACHE_DIR may sit inside a repository — tests do it routinely — and walking into it pulls every other project's graph database into this project's file list. This is the narrow form of a concern that was briefly implemented as refusing any root that contained the cache; refusing a whole root was too blunt, and not walking the cache is what the concern actually asks for. The test fails without the prune: a .go file planted under the cache is otherwise discovered, and "cache" is not in the built-in skip list, so the assertion is not vacuous. Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
This commit is contained in:
1 parent
0d6f26a170
commit
7814dacf15
5 files changed
+79
-11
No files matched your search
@@ -7,12 +7,12 @@
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
int cbm_zstd_compress(const char *src, int srcLen, char *dst, int dstCap, int level) {
|
||||
size_t rc = ZSTD_compress(dst, (size_t)dstCap, src, (size_t)srcLen, level);
|
||||
int64_t cbm_zstd_compress(const char *src, size_t srcLen, char *dst, size_t dstCap, int level) {
|
||||
size_t rc = ZSTD_compress(dst, dstCap, src, srcLen, level);
|
||||
if (ZSTD_isError(rc)) {
|
||||
return 0;
|
||||
}
|
||||
return (int)rc;
|
||||
return (int64_t)rc;
|
||||
}
|
||||
|
||||
int64_t cbm_zstd_decompress(const char *src, size_t srcLen, char *dst, size_t dstCap) {
|
||||
@@ -31,6 +31,6 @@ size_t cbm_zstd_frame_content_size(const char *src, size_t srcLen) {
|
||||
return (size_t)n;
|
||||
}
|
||||
|
||||
size_t cbm_zstd_compress_bound(int inputSize) {
|
||||
return ZSTD_compressBound((size_t)inputSize);
|
||||
size_t cbm_zstd_compress_bound(size_t inputSize) {
|
||||
return ZSTD_compressBound(inputSize);
|
||||
}
|
||||
@@ -5,8 +5,10 @@
|
||||
#include <stdint.h>
|
||||
|
||||
// Zstd compression at specified level (1=fast .. 22=best).
|
||||
// Returns compressed size on success, 0 on error.
|
||||
int cbm_zstd_compress(const char *src, int srcLen, char *dst, int dstCap, int level);
|
||||
// srcLen/dstCap are size_t for the same reason decompression's are: a >2 GiB
|
||||
// database would otherwise wrap through int and hand the encoder a capacity that
|
||||
// disagrees with the real buffer. Returns compressed size on success, 0 on error.
|
||||
int64_t cbm_zstd_compress(const char *src, size_t srcLen, char *dst, size_t dstCap, int level);
|
||||
|
||||
// Zstd decompression. srcLen/dstCap are size_t so a >2 GiB destination capacity
|
||||
// is never truncated (a large DB artifact, or a crafted one, would otherwise
|
||||
@@ -20,6 +22,6 @@ int64_t cbm_zstd_decompress(const char *src, size_t srcLen, char *dst, size_t ds
|
||||
size_t cbm_zstd_frame_content_size(const char *src, size_t srcLen);
|
||||
|
||||
// Maximum compressed size bound for given input size.
|
||||
size_t cbm_zstd_compress_bound(int inputSize);
|
||||
size_t cbm_zstd_compress_bound(size_t inputSize);
|
||||
|
||||
#endif // CBM_ZSTD_STORE_H
|
||||
+27
-1
@@ -13,6 +13,7 @@
|
||||
|
||||
#include "foundation/constants.h"
|
||||
#include "foundation/compat_fs.h"
|
||||
#include "foundation/workspace.h"
|
||||
#include "foundation/platform.h"
|
||||
#ifdef _WIN32
|
||||
#include "foundation/win_utf8.h"
|
||||
@@ -562,6 +563,30 @@ static bool is_safety_core_dir(const char *name) {
|
||||
}
|
||||
|
||||
/* Check if a directory entry should be skipped (hardcoded dirs + gitignore). */
|
||||
/* The cache directory holds every indexed project's graph database. When a custom
|
||||
* CBM_CACHE_DIR sits inside a repository — which happens in tests and is legal in
|
||||
* production — walking into it would pull other projects' databases into this
|
||||
* project's file list. Prune it by absolute path.
|
||||
*
|
||||
* This is the narrow remedy for a concern that was briefly implemented as
|
||||
* refusing any root containing the cache: refusing a whole root was too blunt,
|
||||
* and not walking the cache is what the concern actually asks for. */
|
||||
static bool dir_is_cache_tree(const char *abs_path) {
|
||||
const char *cache = cbm_workspace_cache_dir();
|
||||
if (!cache || !cache[0] || !abs_path || !abs_path[0]) {
|
||||
return false;
|
||||
}
|
||||
size_t n = strlen(cache);
|
||||
while (n > 1 && (cache[n - 1] == '/' || cache[n - 1] == '\\')) {
|
||||
n--;
|
||||
}
|
||||
if (strncmp(abs_path, cache, n) != 0) {
|
||||
return false;
|
||||
}
|
||||
/* Boundary-aware so "<cache>x" is not treated as living under "<cache>". */
|
||||
return abs_path[n] == '\0' || abs_path[n] == '/' || abs_path[n] == '\\';
|
||||
}
|
||||
|
||||
static bool should_skip_directory(const char *entry_name, const char *rel_path,
|
||||
const cbm_discover_opts_t *opts, const cbm_gitignore_t *gitignore,
|
||||
const cbm_gitignore_t *global_gi,
|
||||
@@ -853,7 +878,8 @@ static void walk_dir_process_entry(cbm_dirent_t *entry, const walk_frame_t *fram
|
||||
}
|
||||
|
||||
if (S_ISDIR(st.st_mode)) {
|
||||
if (!should_skip_directory(entry->name, rel_path, opts, gitignore, global_gi, cbmignore,
|
||||
if (!dir_is_cache_tree(abs_path) &&
|
||||
!should_skip_directory(entry->name, rel_path, opts, gitignore, global_gi, cbmignore,
|
||||
frame->local_gi, frame->local_gi_prefix)) {
|
||||
walk_push_subdir(ws, abs_path, rel_path, frame, out);
|
||||
} else {
|
||||
|
||||
@@ -589,14 +589,14 @@ int cbm_artifact_export(const char *db_path, const char *repo_path, const char *
|
||||
}
|
||||
|
||||
/* Compress with zstd */
|
||||
size_t bound = cbm_zstd_compress_bound((int)db_size);
|
||||
size_t bound = cbm_zstd_compress_bound(db_size);
|
||||
char *compressed = malloc(bound);
|
||||
if (!compressed) {
|
||||
free(db_data);
|
||||
return artifact_export_fail("compress", NULL, "alloc_compressed_buffer", 0);
|
||||
}
|
||||
|
||||
int clen = cbm_zstd_compress(db_data, (int)db_size, compressed, (int)bound, compression_level);
|
||||
int64_t clen = cbm_zstd_compress(db_data, db_size, compressed, bound, compression_level);
|
||||
free(db_data);
|
||||
|
||||
if (clen <= 0) {
|
||||
|
||||
@@ -297,6 +297,45 @@ TEST(pattern_dts_full) {
|
||||
|
||||
/* ── File discovery (integration) — cross-platform via test_helpers.h ── */
|
||||
|
||||
/* A custom CBM_CACHE_DIR may legitimately sit inside a repository — tests do it
|
||||
* routinely. Walking into it would pull every other project's graph database into
|
||||
* this project's file list, so the walk prunes it by absolute path. */
|
||||
TEST(discover_prunes_the_cache_tree) {
|
||||
char *base = th_mktempdir("cbm_disc_cache");
|
||||
ASSERT(base != NULL);
|
||||
|
||||
th_write_file(TH_PATH(base, "src/main.go"), "package main\n");
|
||||
/* Source-looking files inside the cache must not be discovered. */
|
||||
th_write_file(TH_PATH(base, "cache/other_project/leaked.go"), "package leaked\n");
|
||||
|
||||
const char *saved = getenv("CBM_CACHE_DIR");
|
||||
char *saved_copy = saved ? strdup(saved) : NULL;
|
||||
char cache_dir[1024];
|
||||
snprintf(cache_dir, sizeof(cache_dir), "%s/cache", base);
|
||||
cbm_setenv("CBM_CACHE_DIR", cache_dir, 1);
|
||||
|
||||
cbm_discover_opts_t opts = {0};
|
||||
cbm_file_info_t *files = NULL;
|
||||
int count = 0;
|
||||
int rc = cbm_discover(base, &opts, &files, &count);
|
||||
|
||||
if (saved_copy) {
|
||||
cbm_setenv("CBM_CACHE_DIR", saved_copy, 1);
|
||||
free(saved_copy);
|
||||
} else {
|
||||
cbm_unsetenv("CBM_CACHE_DIR");
|
||||
}
|
||||
|
||||
ASSERT_EQ(rc, 0);
|
||||
for (int i = 0; i < count; i++) {
|
||||
ASSERT(strstr(files[i].rel_path, "leaked.go") == NULL);
|
||||
}
|
||||
ASSERT_EQ(count, 1); /* only src/main.go */
|
||||
cbm_discover_free(files, count);
|
||||
th_cleanup(base);
|
||||
PASS();
|
||||
}
|
||||
|
||||
TEST(discover_simple) {
|
||||
char *base = th_mktempdir("cbm_disc_simple");
|
||||
ASSERT(base != NULL);
|
||||
@@ -1359,6 +1398,7 @@ TEST(discover_many_nested_gitignores_do_not_exhaust_matcher_ownership) {
|
||||
/* ── Suite ─────────────────────────────────────────────────────── */
|
||||
|
||||
SUITE(discover) {
|
||||
RUN_TEST(discover_prunes_the_cache_tree);
|
||||
/* Directory skip — always */
|
||||
RUN_TEST(skip_git);
|
||||
RUN_TEST(skip_node_modules);
|
||||
|
||||
Reference in new issue
Block a user