mirror of
https://github.com/DeusData/codebase-memory-mcp.git
synced 2026-11-12 09:47:20 +08:00
fix(search_code): error on invalid regex instead of silent empty (#283)
With regex=true, a syntactically invalid pattern (e.g. unclosed group) made the underlying grep fail, which handle_search_code reported as an empty result set — indistinguishable from a legitimate no-match. Validate the user pattern with cbm_regcomp up front and return an explicit "invalid regex" error so callers can tell a broken pattern from zero matches. regex=false is unaffected.
This commit is contained in:
@@ -3536,6 +3536,9 @@ static char *handle_search_code(cbm_mcp_server_t *srv, const char *args) {
|
||||
}
|
||||
|
||||
if (!validate_search_args(root_path, file_pattern)) {
|
||||
if (has_path_filter) {
|
||||
cbm_regfree(&path_regex);
|
||||
}
|
||||
free(root_path);
|
||||
free(pattern);
|
||||
free(project);
|
||||
@@ -3543,6 +3546,27 @@ static char *handle_search_code(cbm_mcp_server_t *srv, const char *args) {
|
||||
return cbm_mcp_text_result("path or file_pattern contains invalid characters", true);
|
||||
}
|
||||
|
||||
/* issue #283: when regex=true, a syntactically invalid pattern (e.g. an
|
||||
* unclosed group) makes the underlying grep fail, which the handler would
|
||||
* otherwise report as an empty result set — indistinguishable from a
|
||||
* legitimate no-match. Validate the user's regex up front and return an
|
||||
* explicit error so callers can tell "broken pattern" from "no matches". */
|
||||
if (use_regex) {
|
||||
cbm_regex_t probe;
|
||||
if (cbm_regcomp(&probe, pattern, CBM_REG_EXTENDED | CBM_REG_NOSUB) != CBM_REG_OK) {
|
||||
if (has_path_filter) {
|
||||
cbm_regfree(&path_regex);
|
||||
}
|
||||
free(root_path);
|
||||
free(pattern);
|
||||
free(project);
|
||||
free(file_pattern);
|
||||
return cbm_mcp_text_result(
|
||||
"invalid regex pattern (regex=true): check for unbalanced (), [], or {}", true);
|
||||
}
|
||||
cbm_regfree(&probe);
|
||||
}
|
||||
|
||||
/* ── Phase 0.5: Multi-word → regex conversion ───────────── */
|
||||
/* If pattern contains whitespace and is not already a regex, convert to a
|
||||
* regex that matches all words in order: "foo bar baz" → "foo.*bar.*baz".
|
||||
|
||||
@@ -739,6 +739,39 @@ TEST(search_code_multi_word) {
|
||||
PASS();
|
||||
}
|
||||
|
||||
/* issue #283: search_code with regex=true and a syntactically invalid pattern
|
||||
* must return an explicit error, not an empty result indistinguishable from a
|
||||
* legitimate no-match. */
|
||||
TEST(search_code_invalid_regex_errors_issue283) {
|
||||
char tmp[512];
|
||||
cbm_mcp_server_t *srv = setup_snippet_server(tmp, sizeof(tmp));
|
||||
ASSERT_NOT_NULL(srv);
|
||||
|
||||
/* Unclosed group under regex=true → must be flagged as an error. */
|
||||
char *resp =
|
||||
cbm_mcp_server_handle(srv, "{\"jsonrpc\":\"2.0\",\"id\":91,\"method\":\"tools/call\","
|
||||
"\"params\":{\"name\":\"search_code\","
|
||||
"\"arguments\":{\"pattern\":\"func(\",\"regex\":true,"
|
||||
"\"project\":\"test-project\"}}}");
|
||||
ASSERT_NOT_NULL(resp);
|
||||
ASSERT_NOT_NULL(strstr(resp, "\"isError\":true"));
|
||||
ASSERT_NOT_NULL(strstr(resp, "invalid regex"));
|
||||
free(resp);
|
||||
|
||||
/* Same pattern as a literal (regex=false) must NOT error. */
|
||||
resp = cbm_mcp_server_handle(srv, "{\"jsonrpc\":\"2.0\",\"id\":92,\"method\":\"tools/call\","
|
||||
"\"params\":{\"name\":\"search_code\","
|
||||
"\"arguments\":{\"pattern\":\"func(\",\"regex\":false,"
|
||||
"\"project\":\"test-project\"}}}");
|
||||
ASSERT_NOT_NULL(resp);
|
||||
ASSERT_TRUE(strstr(resp, "invalid regex") == NULL);
|
||||
free(resp);
|
||||
|
||||
cleanup_snippet_dir(tmp);
|
||||
cbm_mcp_server_free(srv);
|
||||
PASS();
|
||||
}
|
||||
|
||||
TEST(tool_detect_changes_no_project) {
|
||||
cbm_mcp_server_t *srv = cbm_mcp_server_new(NULL);
|
||||
|
||||
@@ -1937,6 +1970,7 @@ SUITE(mcp) {
|
||||
RUN_TEST(tool_search_code_missing_pattern);
|
||||
RUN_TEST(tool_search_code_no_project);
|
||||
RUN_TEST(search_code_multi_word);
|
||||
RUN_TEST(search_code_invalid_regex_errors_issue283);
|
||||
RUN_TEST(tool_detect_changes_no_project);
|
||||
RUN_TEST(tool_manage_adr_no_project);
|
||||
RUN_TEST(tool_manage_adr_get_with_existing_adr);
|
||||
|
||||
Reference in New Issue
Block a user