mirror of
https://github.com/DeusData/codebase-memory-mcp.git
synced 2026-10-22 06:09:35 +08:00
fix(extract,mcp): retain pointer returns and exact signature status
Both merge parents be89d496 and 97ce23f stored only the base type node for C/C++ functions and methods, so const char * and char ** were published as char. The feature parent also marked requested get_code signature output as truncated source and emitted repair-oriented clipping metadata.
Add c_declarator_return_type_text at internal/cbm/extract_defs.c:3154 to retain leading qualifiers and every pointer_declarator in O(D) traversal time with O(return-type bytes) arena storage and checked size arithmetic. Route free-function and method return metadata through it. In src/mcp/mcp.c:13543, treat signature mode as a complete requested representation: retain total_lines, omit truncated/source_clipped/clipped_at_lines, and reuse one signature_mode predicate.
Tests require const char *, char **, unchanged scalar int, C++ method parity, total_lines=300, and absence of false clipping fields. Verified extraction 329/329, C LSP 752/752, token_reduction 56/56, MCP 314/314 under ASan/UBSan; test-leak 1194/1194 with 0 leaks; explicit four-file test-syntax and source safety pass. Clang analyzer reports no diagnostics in changed production ranges.
Signed-off-by: Andrew Hundt <ATHundt@gmail.com>
This commit is contained in:
+118
-2
@@ -3151,6 +3151,122 @@ static TSNode find_c_params(TSNode func_node) {
|
||||
return null_node;
|
||||
}
|
||||
|
||||
static bool c_return_type_size_add(size_t *total, size_t addition) {
|
||||
if (addition > SIZE_MAX - *total) {
|
||||
return false;
|
||||
}
|
||||
*total += addition;
|
||||
return true;
|
||||
}
|
||||
|
||||
/* C-family grammars split a function's declared return type across sibling
|
||||
* type/type_qualifier nodes and the declarator chain. Preserve the exact base
|
||||
* spelling, leading qualifiers, and every pointer layer without imposing a
|
||||
* depth cap: the walk follows one strict child chain, so runtime is O(D) and
|
||||
* temporary memory is O(return-type bytes), where D is declarator depth. */
|
||||
static char *c_declarator_return_type_text(CBMExtractCtx *ctx, TSNode func_node, TSNode type_node) {
|
||||
uint32_t base_start = ts_node_start_byte(type_node);
|
||||
uint32_t base_end = ts_node_end_byte(type_node);
|
||||
if (base_end <= base_start) {
|
||||
return cbm_node_text(ctx->arena, type_node, ctx->source);
|
||||
}
|
||||
|
||||
TSNode declarator = ts_node_child_by_field_name(func_node, TS_FIELD("declarator"));
|
||||
if (ts_node_is_null(declarator)) {
|
||||
return cbm_node_text(ctx->arena, type_node, ctx->source);
|
||||
}
|
||||
|
||||
size_t pointer_count = 0;
|
||||
for (TSNode node = declarator; !ts_node_is_null(node);
|
||||
node = ts_node_child_by_field_name(node, TS_FIELD("declarator"))) {
|
||||
if (strcmp(ts_node_type(node), "pointer_declarator") == 0) {
|
||||
if (pointer_count == SIZE_MAX) {
|
||||
ctx->result->has_error = true;
|
||||
ctx->result->error_msg =
|
||||
cbm_arena_strdup(ctx->arena, "C return-type pointer depth exceeds size limit");
|
||||
return cbm_node_text(ctx->arena, type_node, ctx->source);
|
||||
}
|
||||
pointer_count++;
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t declarator_start = ts_node_start_byte(declarator);
|
||||
size_t qualifier_bytes = 0;
|
||||
size_t qualifier_count = 0;
|
||||
uint32_t child_count = ts_node_named_child_count(func_node);
|
||||
for (uint32_t i = 0; i < child_count; i++) {
|
||||
TSNode child = ts_node_named_child(func_node, i);
|
||||
if (strcmp(ts_node_type(child), "type_qualifier") != 0 ||
|
||||
ts_node_end_byte(child) > declarator_start) {
|
||||
continue;
|
||||
}
|
||||
uint32_t start = ts_node_start_byte(child);
|
||||
uint32_t end = ts_node_end_byte(child);
|
||||
if (end > start) {
|
||||
size_t len = (size_t)(end - start);
|
||||
if (len > SIZE_MAX - qualifier_bytes || qualifier_count == SIZE_MAX) {
|
||||
ctx->result->has_error = true;
|
||||
ctx->result->error_msg =
|
||||
cbm_arena_strdup(ctx->arena, "C return-type qualifier size exceeds limit");
|
||||
return cbm_node_text(ctx->arena, type_node, ctx->source);
|
||||
}
|
||||
qualifier_bytes += len;
|
||||
qualifier_count++;
|
||||
}
|
||||
}
|
||||
|
||||
if (pointer_count == 0 && qualifier_count == 0) {
|
||||
return cbm_node_text(ctx->arena, type_node, ctx->source);
|
||||
}
|
||||
|
||||
size_t base_len = (size_t)(base_end - base_start);
|
||||
size_t result_len = base_len;
|
||||
if (!c_return_type_size_add(&result_len, qualifier_bytes) ||
|
||||
!c_return_type_size_add(&result_len, qualifier_count) ||
|
||||
(pointer_count > 0 && !c_return_type_size_add(&result_len, SKIP_ONE)) ||
|
||||
!c_return_type_size_add(&result_len, pointer_count) ||
|
||||
!c_return_type_size_add(&result_len, NULL_TERM)) {
|
||||
ctx->result->has_error = true;
|
||||
ctx->result->error_msg =
|
||||
cbm_arena_strdup(ctx->arena, "C return-type metadata exceeds addressable size");
|
||||
return cbm_node_text(ctx->arena, type_node, ctx->source);
|
||||
}
|
||||
char *result = cbm_arena_alloc(ctx->arena, result_len);
|
||||
if (!result) {
|
||||
ctx->result->has_error = true;
|
||||
ctx->result->error_msg =
|
||||
cbm_arena_strdup(ctx->arena, "C return-type metadata allocation failed");
|
||||
return cbm_node_text(ctx->arena, type_node, ctx->source);
|
||||
}
|
||||
|
||||
size_t pos = 0;
|
||||
for (uint32_t i = 0; i < child_count; i++) {
|
||||
TSNode child = ts_node_named_child(func_node, i);
|
||||
if (strcmp(ts_node_type(child), "type_qualifier") != 0 ||
|
||||
ts_node_end_byte(child) > declarator_start) {
|
||||
continue;
|
||||
}
|
||||
uint32_t start = ts_node_start_byte(child);
|
||||
uint32_t end = ts_node_end_byte(child);
|
||||
if (end <= start) {
|
||||
continue;
|
||||
}
|
||||
size_t len = (size_t)(end - start);
|
||||
memcpy(result + pos, ctx->source + start, len);
|
||||
pos += len;
|
||||
result[pos++] = ' ';
|
||||
}
|
||||
memcpy(result + pos, ctx->source + base_start, base_len);
|
||||
pos += base_len;
|
||||
if (pointer_count > 0) {
|
||||
result[pos++] = ' ';
|
||||
memset(result + pos, '*', pointer_count);
|
||||
pos += pointer_count;
|
||||
}
|
||||
result[pos] = '\0';
|
||||
return result;
|
||||
}
|
||||
|
||||
// C++: resolve trailing return type (auto f() -> Type) on a declarator node.
|
||||
// Updates def->return_type and def->return_types if trailing type found.
|
||||
static void resolve_cpp_trailing_return(CBMArena *a, TSNode func_node, const char *source,
|
||||
@@ -3304,7 +3420,7 @@ static void extract_func_def(CBMExtractCtx *ctx, TSNode node, const CBMLangSpec
|
||||
for (const char **f = rt_fields; *f; f++) {
|
||||
TSNode rt = ts_node_child_by_field_name(func_node, *f, (uint32_t)strlen(*f));
|
||||
if (!ts_node_is_null(rt)) {
|
||||
def.return_type = cbm_node_text(a, rt, ctx->source);
|
||||
def.return_type = c_declarator_return_type_text(ctx, func_node, rt);
|
||||
def.return_types = extract_return_types(ctx, rt);
|
||||
break;
|
||||
}
|
||||
@@ -4311,7 +4427,7 @@ static void push_method_def(CBMExtractCtx *ctx, TSNode child, TSNode class_node,
|
||||
for (const char **f = rt_fields; *f; f++) {
|
||||
TSNode rt = ts_node_child_by_field_name(child, *f, (uint32_t)strlen(*f));
|
||||
if (!ts_node_is_null(rt)) {
|
||||
def.return_type = cbm_node_text(a, rt, ctx->source);
|
||||
def.return_type = c_declarator_return_type_text(ctx, child, rt);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
+9
-4
@@ -13540,6 +13540,7 @@ static char *build_snippet_response(cbm_mcp_server_t *srv, cbm_node_t *node,
|
||||
int end = node->end_line > start ? node->end_line : start + SNIPPET_DEFAULT_LINES;
|
||||
int total_lines = end - start + 1;
|
||||
bool truncated = false;
|
||||
bool signature_mode = mode && strcmp(mode, "signature") == 0;
|
||||
char *source = NULL;
|
||||
char *source_tail = NULL;
|
||||
|
||||
@@ -13555,8 +13556,8 @@ static char *build_snippet_response(cbm_mcp_server_t *srv, cbm_node_t *node,
|
||||
bool path_ok = path_len >= 0 && (size_t)path_len < apsz &&
|
||||
cbm_path_within_root(root_path, abs_path);
|
||||
if (path_ok) {
|
||||
if (mode && strcmp(mode, "signature") == 0) {
|
||||
truncated = true;
|
||||
if (signature_mode) {
|
||||
/* Source omission is the requested representation, not truncation. */
|
||||
} else if (mode && strcmp(mode, "head_tail") == 0 && max_lines > 0 &&
|
||||
total_lines > max_lines) {
|
||||
int head_count = (max_lines * CBM_SNIPPET_HEAD_PERCENT) /
|
||||
@@ -13600,7 +13601,7 @@ static char *build_snippet_response(cbm_mcp_server_t *srv, cbm_node_t *node,
|
||||
yyjson_mut_obj_add_int(doc, root_obj, "start_line", start);
|
||||
yyjson_mut_obj_add_int(doc, root_obj, "end_line", end);
|
||||
|
||||
if (mode && strcmp(mode, "signature") == 0) {
|
||||
if (signature_mode) {
|
||||
/* Signature mode: source omitted; signature comes from properties below */
|
||||
} else if (mode && strcmp(mode, "head_tail") == 0 && source && source_tail) {
|
||||
/* Combine head + marker + tail */
|
||||
@@ -13641,6 +13642,10 @@ static char *build_snippet_response(cbm_mcp_server_t *srv, cbm_node_t *node,
|
||||
yyjson_mut_obj_add_bool(doc, root_obj, "source_clipped", true);
|
||||
yyjson_mut_obj_add_int(doc, root_obj, "clipped_at_lines", max_lines);
|
||||
yyjson_mut_obj_add_int(doc, root_obj, "total_lines", total_lines);
|
||||
} else if (signature_mode) {
|
||||
/* Preserve useful size context without claiming requested source was
|
||||
* clipped or suggesting max_lines=0 is needed to repair the result. */
|
||||
yyjson_mut_obj_add_int(doc, root_obj, "total_lines", total_lines);
|
||||
}
|
||||
|
||||
/* match_method — omitted for exact matches */
|
||||
@@ -13652,7 +13657,7 @@ static char *build_snippet_response(cbm_mcp_server_t *srv, cbm_node_t *node,
|
||||
* props_doc is freed AFTER serialization since yyjson_mut_obj_add_str
|
||||
* stores pointers into it (zero-copy). */
|
||||
yyjson_doc *props_doc = NULL;
|
||||
bool include_properties = !compact || (mode && strcmp(mode, "signature") == 0);
|
||||
bool include_properties = !compact || signature_mode;
|
||||
if (include_properties && node->properties_json && node->properties_json[0] != '\0') {
|
||||
props_doc = yyjson_read(node->properties_json, strlen(node->properties_json), 0);
|
||||
if (props_doc) {
|
||||
|
||||
@@ -908,6 +908,40 @@ TEST(c_function) {
|
||||
PASS();
|
||||
}
|
||||
|
||||
TEST(c_function_return_type_preserves_pointer_and_qualifier) {
|
||||
CBMFileResult *r =
|
||||
extract("static const char *text_end(const char *text) { return text; }\n"
|
||||
"char **table(void) { return 0; }\n"
|
||||
"int scalar(void) { return 0; }\n",
|
||||
CBM_LANG_C, "t", "returns.c");
|
||||
ASSERT_NOT_NULL(r);
|
||||
ASSERT_FALSE(r->has_error);
|
||||
|
||||
const CBMDefinition *text_end = NULL;
|
||||
const CBMDefinition *table = NULL;
|
||||
const CBMDefinition *scalar = NULL;
|
||||
for (int i = 0; i < r->defs.count; i++) {
|
||||
const CBMDefinition *def = &r->defs.items[i];
|
||||
if (strcmp(def->name, "text_end") == 0) {
|
||||
text_end = def;
|
||||
} else if (strcmp(def->name, "table") == 0) {
|
||||
table = def;
|
||||
} else if (strcmp(def->name, "scalar") == 0) {
|
||||
scalar = def;
|
||||
}
|
||||
}
|
||||
|
||||
ASSERT_NOT_NULL(text_end);
|
||||
ASSERT_NOT_NULL(table);
|
||||
ASSERT_NOT_NULL(scalar);
|
||||
ASSERT_STR_EQ(text_end->return_type, "const char *");
|
||||
ASSERT_STR_EQ(table->return_type, "char **");
|
||||
ASSERT_STR_EQ(scalar->return_type, "int");
|
||||
|
||||
cbm_free_result(r);
|
||||
PASS();
|
||||
}
|
||||
|
||||
TEST(c_struct) {
|
||||
CBMFileResult *r =
|
||||
extract("struct Point { int x; int y; };\nvoid init_point(struct Point *p) { p->x = 0; }\n",
|
||||
@@ -932,6 +966,36 @@ TEST(cpp_class) {
|
||||
PASS();
|
||||
}
|
||||
|
||||
TEST(cpp_method_return_type_preserves_pointer_and_qualifier) {
|
||||
CBMFileResult *r =
|
||||
extract("class Text {\n"
|
||||
"public:\n"
|
||||
" const char *end() { return nullptr; }\n"
|
||||
" char **table() { return nullptr; }\n"
|
||||
"};\n",
|
||||
CBM_LANG_CPP, "t", "text.cpp");
|
||||
ASSERT_NOT_NULL(r);
|
||||
ASSERT_FALSE(r->has_error);
|
||||
|
||||
const CBMDefinition *end = NULL;
|
||||
const CBMDefinition *table = NULL;
|
||||
for (int i = 0; i < r->defs.count; i++) {
|
||||
const CBMDefinition *def = &r->defs.items[i];
|
||||
if (strcmp(def->name, "end") == 0) {
|
||||
end = def;
|
||||
} else if (strcmp(def->name, "table") == 0) {
|
||||
table = def;
|
||||
}
|
||||
}
|
||||
ASSERT_NOT_NULL(end);
|
||||
ASSERT_NOT_NULL(table);
|
||||
ASSERT_STR_EQ(end->return_type, "const char *");
|
||||
ASSERT_STR_EQ(table->return_type, "char **");
|
||||
|
||||
cbm_free_result(r);
|
||||
PASS();
|
||||
}
|
||||
|
||||
/* ═══════════════════════════════════════════════════════════════════
|
||||
* Group C: Scripting / Dynamic Languages
|
||||
* ═══════════════════════════════════════════════════════════════════ */
|
||||
@@ -5643,8 +5707,10 @@ SUITE(extraction) {
|
||||
RUN_TEST(dlang_struct);
|
||||
RUN_TEST(zig_function);
|
||||
RUN_TEST(c_function);
|
||||
RUN_TEST(c_function_return_type_preserves_pointer_and_qualifier);
|
||||
RUN_TEST(c_struct);
|
||||
RUN_TEST(cpp_class);
|
||||
RUN_TEST(cpp_method_return_type_preserves_pointer_and_qualifier);
|
||||
|
||||
/* Scripting */
|
||||
RUN_TEST(python_function);
|
||||
|
||||
@@ -387,8 +387,12 @@ TEST(snippet_signature_mode) {
|
||||
ASSERT_NOT_NULL(strstr(resp, "def large_function(arg1, arg2, arg3)"));
|
||||
/* Should NOT contain full source body */
|
||||
ASSERT_NULL(strstr(resp, "step_050"));
|
||||
/* Should indicate total size */
|
||||
ASSERT_NOT_NULL(strstr(resp, "\"total_lines\""));
|
||||
/* Signature is a complete requested representation, not clipped source.
|
||||
* Retain size context without telling callers to repair a non-problem. */
|
||||
ASSERT_NOT_NULL(strstr(resp, "\"total_lines\":300"));
|
||||
ASSERT_NULL(strstr(resp, "\"truncated\":true"));
|
||||
ASSERT_NULL(strstr(resp, "\"source_clipped\":true"));
|
||||
ASSERT_NULL(strstr(resp, "\"clipped_at_lines\""));
|
||||
|
||||
free(resp);
|
||||
cbm_mcp_server_free(srv);
|
||||
|
||||
Reference in New Issue
Block a user