Skip to content

feat: add c language support #167

Open
chinaux wants to merge 2 commits intomainfrom
feat/add_c_api
Open

feat: add c language support #167
chinaux wants to merge 2 commits intomainfrom
feat/add_c_api

Conversation

@chinaux
Copy link
Collaborator

@chinaux chinaux commented Feb 25, 2026

Summary

Add C language bindings.

Changes

src/c_api/c_api.cc

  • Implement C API wrapper in C++ to expose functionality to C

src/include/zvec/c_api.h

  • Add C-compatible header file defining the public C API

examples/c_api/

  • Add example programs demonstrating usage of the C API

Testing

  • Add C API unit tests in tests/c_api/c_api_test.c

@Cuiyus
Copy link
Collaborator

Cuiyus commented Feb 26, 2026

@greptile

@greptile-apps
Copy link

greptile-apps bot commented Feb 26, 2026

Greptile Summary

This PR adds comprehensive C language bindings to the zvec project, including a C API wrapper (c_api.cc), public C header (c_api.h), examples, and test suite.

Key additions:

  • Complete C API covering collections, documents, schemas, queries, and configuration
  • 6 example programs demonstrating API usage
  • Comprehensive test suite with 2,350+ lines of test code
  • Proper CMake integration with BUILD_EXAMPLES option

Critical issue:

  • Memory management bug throughout c_api.cc: functions allocate memory with new[] but document/use free() for deallocation, causing undefined behavior. This affects copy_string(), zvec_string_create(), zvec_doc_get_pk_copy(), and all error message handling.

The implementation is otherwise well-structured with thread-safe error handling, proper API versioning, and comprehensive functionality coverage.

Confidence Score: 1/5

  • This PR contains critical memory management bugs that will cause undefined behavior and potential crashes in production
  • Score reflects the severity of memory allocation/deallocation mismatches (mixing new[]/delete[] with malloc/free) that occur in multiple core functions throughout the C API implementation. These bugs will cause undefined behavior on most platforms and must be fixed before merging
  • src/c_api/c_api.cc requires immediate attention to fix memory management issues. All functions that allocate strings need to be updated to use consistent allocation/deallocation methods

Important Files Changed

Filename Overview
src/c_api/c_api.cc Implements C API wrapper with critical memory management bugs - mixing new[]/delete[] with malloc/free causes undefined behavior in string allocation functions
src/include/zvec/c_api.h Comprehensive C API header with proper structure, though documentation inconsistencies exist regarding memory deallocation methods
CMakeLists.txt Added BUILD_EXAMPLES option and examples directory - straightforward and safe changes
examples/c_api/basic_example.c Demonstrates C API usage but contains memory deallocation bug at line 227 (uses free() on memory allocated with new[])
tests/c_api/c_api_test.c Comprehensive test suite covering major C API functionality

Class Diagram

%%{init: {'theme': 'neutral'}}%%
classDiagram
    class ZVec_C_API {
        +zvec_initialize()
        +zvec_shutdown()
        +zvec_get_version()
    }
    
    class Collection_API {
        +zvec_collection_create()
        +zvec_collection_open()
        +zvec_collection_insert()
        +zvec_collection_query()
        +zvec_collection_flush()
    }
    
    class Schema_API {
        +zvec_collection_schema_create()
        +zvec_field_schema_create()
        +zvec_field_schema_set_hnsw_index()
        +zvec_field_schema_set_invert_index()
    }
    
    class Document_API {
        +zvec_doc_create()
        +zvec_doc_set_pk()
        +zvec_doc_add_field()
        +zvec_doc_get_pk_copy()
    }
    
    class Index_API {
        +zvec_index_params_hnsw_create()
        +zvec_index_params_invert_create()
    }
    
    class Config_API {
        +zvec_config_data_create()
        +zvec_config_log_create()
    }
    
    class Memory_Management {
        +zvec_free()
        +zvec_free_string()
        +zvec_free_str()
        +zvec_docs_free()
    }
    
    ZVec_C_API --> Collection_API
    ZVec_C_API --> Config_API
    Collection_API --> Schema_API
    Collection_API --> Document_API
    Schema_API --> Index_API
    Document_API --> Memory_Management
    Collection_API --> Memory_Management
Loading

Last reviewed commit: 67ff247

Copy link

@greptile-apps greptile-apps bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

19 files reviewed, 4 comments

Edit Code Review Agent Settings | Greptile

Comment on lines +652 to +658
static char *copy_string(const std::string &str) {
if (str.empty()) return nullptr;

char *copy = new char[str.length() + 1];
strcpy(copy, str.c_str());
return copy;
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Memory allocated with new[] must be freed with delete[], not free(). This function is used in multiple places (e.g., zvec_get_last_error, zvec_doc_get_pk_copy) where the returned pointers are documented to be freed with free() or zvec_free (which calls free()), causing undefined behavior.

Suggested change
static char *copy_string(const std::string &str) {
if (str.empty()) return nullptr;
char *copy = new char[str.length() + 1];
strcpy(copy, str.c_str());
return copy;
}
// Helper function: copy strings
static char *copy_string(const std::string &str) {
if (str.empty()) return nullptr;
char *copy = static_cast<char*>(malloc(str.length() + 1));
strcpy(copy, str.c_str());
return copy;
}

Comment on lines +2906 to +2908
char *result = new char[pk.length() + 1];
strcpy(result, pk.c_str());
return result;
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Memory allocated with new[] but documented (line 1966 in c_api.h) to be freed with free(). This causes undefined behavior - mixing new[]/delete[] with malloc/free.

Suggested change
char *result = new char[pk.length() + 1];
strcpy(result, pk.c_str());
return result;
char *result = static_cast<char*>(malloc(pk.length() + 1));
strcpy(result, pk.c_str());
return result;

Comment on lines +138 to +174
ZVecString *zvec_string_create(const char *str) {
if (!str) {
set_last_error_details(ZVEC_ERROR_INVALID_ARGUMENT,
"String pointer cannot be null", __FILE__, __LINE__,
__FUNCTION__);
return nullptr;
}

ZVecString *zstr = nullptr;
char *data_buffer = nullptr;

try {
size_t len = strlen(str);
zstr = new ZVecString();
data_buffer = new char[len + 1];
strcpy(const_cast<char *>(data_buffer), str);

zstr->data = data_buffer;
zstr->length = len;
zstr->capacity = len + 1;

return zstr;

} catch (const std::exception &e) {
if (data_buffer) {
delete[] data_buffer;
}
if (zstr) {
delete zstr;
}

set_last_error_details(ZVEC_ERROR_INTERNAL_ERROR,
std::string("String creation failed: ") + e.what(),
__FILE__, __LINE__, __FUNCTION__);
return nullptr;
}
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Memory allocated with new[] at line 152 but should be freed with delete[]. Since the API provides zvec_free_string which uses delete[], this is correct. However, zvec_string_create is also used in zvec_config_file_log_create where the memory is later freed with zvec_free_str (which calls free()) instead of delete[].

Suggested change
ZVecString *zvec_string_create(const char *str) {
if (!str) {
set_last_error_details(ZVEC_ERROR_INVALID_ARGUMENT,
"String pointer cannot be null", __FILE__, __LINE__,
__FUNCTION__);
return nullptr;
}
ZVecString *zstr = nullptr;
char *data_buffer = nullptr;
try {
size_t len = strlen(str);
zstr = new ZVecString();
data_buffer = new char[len + 1];
strcpy(const_cast<char *>(data_buffer), str);
zstr->data = data_buffer;
zstr->length = len;
zstr->capacity = len + 1;
return zstr;
} catch (const std::exception &e) {
if (data_buffer) {
delete[] data_buffer;
}
if (zstr) {
delete zstr;
}
set_last_error_details(ZVEC_ERROR_INTERNAL_ERROR,
std::string("String creation failed: ") + e.what(),
__FILE__, __LINE__, __FUNCTION__);
return nullptr;
}
}
ZVecString *zvec_string_create(const char *str) {
if (!str) {
set_last_error_details(ZVEC_ERROR_INVALID_ARGUMENT,
"String pointer cannot be null", __FILE__, __LINE__,
__FUNCTION__);
return nullptr;
}
ZVecString *zstr = nullptr;
char *data_buffer = nullptr;
try {
size_t len = strlen(str);
zstr = new ZVecString();
data_buffer = static_cast<char*>(malloc(len + 1));
strcpy(data_buffer, str);
zstr->data = data_buffer;
zstr->length = len;
zstr->capacity = len + 1;
return zstr;
} catch (const std::exception &e) {
if (data_buffer) {
free(data_buffer);
}
if (zstr) {
delete zstr;
}
set_last_error_details(ZVEC_ERROR_INTERNAL_ERROR,
std::string("String creation failed: ") + e.what(),
__FILE__, __LINE__, __FUNCTION__);
return nullptr;
}
}

Comment on lines +428 to +434
void zvec_config_file_log_destroy(ZVecFileLogConfig *config) {
if (config) {
if (config->dir.data) zvec_free_str(config->dir.data);
if (config->basename.data) zvec_free_str(config->basename.data);
delete config;
}
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Calls zvec_free_str which uses free() to deallocate memory that was allocated with new char[] in zvec_string_create. This causes undefined behavior.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants