Skip to content

fix(cpp): fix memory leaks and null dereference crashes - #879

Merged
ColinLeeo merged 1 commit into
developfrom
fix_memory_crash
Jul 29, 2026
Merged

fix(cpp): fix memory leaks and null dereference crashes#879
ColinLeeo merged 1 commit into
developfrom
fix_memory_crash

Conversation

@ColinLeeo

Copy link
Copy Markdown
Contributor

Summary

Fix 4 classes of memory/crash bugs discovered during a systematic scan of the C++ codebase.

Changes

1. ChunkReader double-free (src/reader/chunk_reader.cc)

reset() frees the wrapped buffer via mem_free(file_data_buf) but does not clear the wrapped pointer in ByteStream. When destroy() is subsequently called, get_wrapped_buf() returns the already-freed pointer and mem_free(buf) is called again → double-free crash.

Fix: Call in_stream_.clear_wrapped_buf() immediately after mem_free() to null out the stale pointer. destroy() already has its own null-guard, so this prevents the double-free.

2. ChunkReader read-failure memory leak (src/reader/chunk_reader.cc)

load_by_meta() allocates file_data_buf via mem_alloc(), then calls read_file_->read(). The original code only freed the buffer on the partial-success path (IS_SUCC(ret) && ret_read_len < MIN_SERIALIZED_SIZE). If read() itself returns an error, the buffer is never freed and never passed to wrap_from()memory leak for every failed chunk load.

Fix: Add an early-exit path after read(): if !IS_SUCC(ret), free the buffer and return immediately.

3. LZ4Compressor error-path leaks (src/compress/lz4_compressor.cc)

compress() allocates compressed_buf_ via mem_alloc(), then has two error paths that do not free it:

  • LZ4_compress_default returns ≤ 0 → compressed_buf_ leaked
  • mem_realloc returns nullptr → compressed_buf_ leaked

Fix: Add mem_free(compressed_buf_); compressed_buf_ = nullptr; on both error paths.

4. C wrapper string getter null dereference (src/cwrapper/tsfile_cwrapper.cc)

Both tsfile_result_set_get_value_by_{name,index}_string() call r->get_value<common::String*>() which delegates to Field::get_string_value(). For non-STRING/TEXT/BLOB column types, this returns nullptr. The code then unconditionally dereferences ret->len_SIGSEGV.

Fix: Add a null check before the dereference, returning nullptr (which the Python caller already handles correctly, returning None).

Verification

  • All 721 C++ tests pass (TsFile_Test with ASan/Debug)
  • clang-format applied via spotless:apply

Files Changed

File +/−
cpp/src/reader/chunk_reader.cc +7/−1
cpp/src/compress/lz4_compressor.cc +4/−0
cpp/src/cwrapper/tsfile_cwrapper.cc +6/−0

- ChunkReader::reset() now clears wrapped buffer pointer after free to
  prevent double-free in subsequent destroy()
- ChunkReader::load_by_meta() now frees allocated buffer and returns
  early on read failure, preventing memory leak
- LZ4Compressor::compress() now frees compressed_buf_ on both
  LZ4_compress_default failure and mem_realloc failure paths
- C wrapper string getters (by_name/by_index) now null-check the
  return value of get_value<String*>() before dereferencing,
  preventing crash on non-STRING/TEXT/BLOB column types
@codecov-commenter

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 0% with 10 lines in your changes missing coverage. Please review.
✅ Project coverage is 60.47%. Comparing base (a2fdf71) to head (b3a0002).

Files with missing lines Patch % Lines
cpp/src/compress/lz4_compressor.cc 0.00% 4 Missing ⚠️
cpp/src/reader/chunk_reader.cc 0.00% 2 Missing and 2 partials ⚠️
cpp/src/cwrapper/tsfile_cwrapper.cc 0.00% 0 Missing and 2 partials ⚠️
Additional details and impacted files
@@             Coverage Diff             @@
##           develop     #879      +/-   ##
===========================================
- Coverage    60.48%   60.47%   -0.02%     
===========================================
  Files          744      744              
  Lines        49014    49021       +7     
  Branches      7784     7787       +3     
===========================================
- Hits         29646    29643       -3     
- Misses       17966    17972       +6     
- Partials      1402     1406       +4     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

This PR fixes several C++ robustness issues in TsFile’s reader/compression/C-wrapper layers by addressing concrete memory-management bugs (double-free/leaks) and preventing a null dereference when requesting string values via the C API.

Changes:

  • Prevent double-free in ChunkReader::reset() by clearing the wrapped buffer pointer after freeing it.
  • Fix a read-failure leak in ChunkReader::load_by_meta() by freeing the allocated buffer on read() error and returning early.
  • Fix LZ4Compressor::compress() error-path leaks by freeing compressed_buf_ when compression or realloc fails.
  • Add null guards in C-wrapper string getters to avoid dereferencing a null common::String*.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated no comments.

File Description
cpp/src/reader/chunk_reader.cc Avoids double-free on reset and plugs a buffer leak when chunk-header reads fail.
cpp/src/compress/lz4_compressor.cc Frees the internal compression buffer on LZ4 failure paths to prevent leaks.
cpp/src/cwrapper/tsfile_cwrapper.cc Returns nullptr instead of crashing when a non-string column is requested as string.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@ColinLeeo
ColinLeeo merged commit 94f6dac into develop Jul 29, 2026
30 checks passed
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