Skip to content

fix(json_wrapper): fix undefined behavior in copy constructor#1715

Open
LHT129 wants to merge 1 commit intoantgroup:mainfrom
LHT129:2026-03-13-修复-jsonwrapper-拷贝构造函数-ub

Hidden character warning

The head ref may contain hidden characters: "2026-03-13-\u4fee\u590d-jsonwrapper-\u62f7\u8d1d\u6784\u9020\u51fd\u6570-ub"
Open

fix(json_wrapper): fix undefined behavior in copy constructor#1715
LHT129 wants to merge 1 commit intoantgroup:mainfrom
LHT129:2026-03-13-修复-jsonwrapper-拷贝构造函数-ub

Conversation

@LHT129
Copy link
Copy Markdown
Collaborator

@LHT129 LHT129 commented Mar 18, 2026

Summary

Fix undefined behavior in JsonWrapper copy constructor that could cause random crashes or memory corruption.

Changes

  • Use member initializer list in copy constructor to properly initialize members
  • Remove incorrect cleanup code (if (owns_json_) { delete json_; }) from copy constructor
  • Add unit tests for copy constructor scenarios

Problem

The copy constructor incorrectly checked owns_json_ and deleted json_ before they were initialized:

JsonWrapper::JsonWrapper(const JsonWrapper& other) {
    if (owns_json_) {    // UB: owns_json_ is uninitialized
        delete json_;    // UB: json_ is uninitialized, may delete wild pointer
    }
    // ...
}

Solution

Use member initializer list to properly initialize members:

JsonWrapper::JsonWrapper(const JsonWrapper& other)
    : json_(new nlohmann::json()), owns_json_(true) {
    if (other.json_ != nullptr) {
        *json_ = *other.json_;
    }
}

Testing

Added 6 unit tests covering:

  • Copy empty wrapper
  • Copy with string data
  • Copy with integer data
  • Copy independence (modification does not affect original)
  • Copy nested JSON
  • Copy from parsed JSON with exception disabled

All tests pass.

Related Issues

@LHT129 LHT129 self-assigned this Mar 18, 2026
Copilot AI review requested due to automatic review settings March 18, 2026 11:27
@LHT129 LHT129 added kind/bug Something isn't working version/1.0 labels Mar 18, 2026
@gemini-code-assist
Copy link
Copy Markdown
Contributor

Summary of Changes

Hello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request addresses a critical bug in the JsonWrapper's copy constructor, which previously exhibited undefined behavior due to improper member initialization and premature memory deallocation. The changes ensure robust and predictable copying of JsonWrapper objects, preventing potential runtime issues and enhancing the overall stability of the component. Comprehensive unit tests have been introduced to verify the correctness of the fix.

Highlights

  • Undefined Behavior Fix: Resolved undefined behavior in the JsonWrapper copy constructor that could lead to random crashes or memory corruption.
  • Constructor Refactoring: Modified the copy constructor to properly initialize members using a member initializer list, ensuring correct object state upon construction.
  • Code Cleanup: Removed incorrect cleanup code from the copy constructor that was attempting to delete uninitialized pointers.
  • Test Coverage: Added six new unit tests specifically for the copy constructor to validate its behavior across various scenarios, including empty wrappers, different data types, nested JSON, and independence.

🧠 New Feature in Public Preview: You can now enable Memory to help Gemini Code Assist learn from your team's feedback. This makes future code reviews more consistent and personalized to your project's style. Click here to enable Memory in your admin console.

Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for GitHub and other Google products, sign up here.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link
Copy Markdown
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request correctly fixes undefined behavior in the JsonWrapper copy constructor by using a member initializer list. The original implementation accessed uninitialized members, which could lead to crashes or memory corruption. The fix is sound and is accompanied by a good set of unit tests that verify the correctness of the copy operation, including deep copy semantics. I have one suggestion to make the copy constructor slightly more efficient and idiomatic.

Comment on lines 33 to 36
JsonWrapper::JsonWrapper(const JsonWrapper& other) : json_(new nlohmann::json()), owns_json_(true) {
if (other.json_ != nullptr) {
*json_ = *other.json_;
}
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.

medium

While the current implementation is correct, it can be made more efficient and concise. You can use the nlohmann::json copy constructor directly in the member initializer list. This avoids creating a default-constructed json object first and then assigning to it, performing the initialization in a single step.

JsonWrapper::JsonWrapper(const JsonWrapper& other)
    : json_(other.json_ ? new nlohmann::json(*other.json_) : new nlohmann::json()),
      owns_json_(true) {
}

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Good suggestion! Updated the code to use ternary operator in the member initializer list. This is more concise and efficient, performing the initialization in a single step. Fixed in commit 44a25df.

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Fixes undefined behavior in JsonWrapper’s copy constructor that could delete uninitialized members, and adds regression tests to validate safe, deep-copy behavior.

Changes:

  • Reworked JsonWrapper copy constructor to use a member initializer list and removed invalid pre-initialization cleanup.
  • Added Catch2 unit tests covering several copy-constructor scenarios (empty, primitives, nested JSON, independence).

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.

File Description
src/json_wrapper.cpp Fixes copy-constructor UB by initializing members before use and removing erroneous deletion of uninitialized pointers.
tests/test_json_wrapper.cpp Adds unit tests to exercise copy behavior across multiple JSON shapes and usage patterns.

Comment on lines +55 to +60
TEST_CASE("JsonWrapper Copy From Parsed With Exception Disabled", "[ft][json_wrapper]") {
auto w1 = vsag::JsonWrapper::Parse(R"({"valid": "json"})", false);
vsag::JsonWrapper w2 = w1;
CHECK(w2.Contains("valid"));
CHECK(w2["valid"].GetString() == "json");
} No newline at end of file
Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Addressed! I've renamed the test and updated it to test copying a non-owning wrapper obtained from operator[], which is a more meaningful scenario for the copy constructor.

Copilot AI review requested due to automatic review settings March 19, 2026 03:32
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Fixes undefined behavior in vsag::JsonWrapper’s copy constructor (uninitialized member access during construction), reducing risk of crashes/memory corruption in JSON parameter/config handling.

Changes:

  • Reworks JsonWrapper copy constructor to initialize members via an initializer list.
  • Removes invalid “cleanup” logic from the copy constructor.
  • Adds Catch2 functional tests covering several copy-construction scenarios.

Reviewed changes

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

File Description
src/json_wrapper.cpp Updates JsonWrapper copy constructor implementation to avoid UB and deep-copy the underlying JSON.
tests/test_json_wrapper.cpp Adds functional tests intended to validate copy-constructor behavior and copy independence.

Comment on lines +33 to +35
JsonWrapper::JsonWrapper(const JsonWrapper& other)
: json_(other.json_ != nullptr ? new nlohmann::json(*other.json_) : new nlohmann::json()),
owns_json_(true) {
Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Good suggestion! I've simplified the copy constructor to use a more straightforward approach: initialize json_ to nullptr first, then conditionally allocate memory in the body. This keeps all lines under the 100-column limit and improves readability.

Comment on lines +55 to +57
TEST_CASE("JsonWrapper Copy From Parsed With Exception Disabled", "[ft][json_wrapper]") {
auto w1 = vsag::JsonWrapper::Parse(R"({"valid": "json"})", false);
vsag::JsonWrapper w2 = w1;
Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Good point! I've renamed the test to "JsonWrapper Copy Non-owning Wrapper" and changed it to test copying a wrapper obtained from operator[] (which returns a non-owning wrapper). This provides more meaningful test coverage for the copy constructor with different ownership scenarios.

@LHT129 LHT129 force-pushed the 2026-03-13-修复-jsonwrapper-拷贝构造函数-ub branch from 0cb28dc to daf151b Compare March 19, 2026 03:41
@pull-request-size pull-request-size bot added size/L and removed size/M labels Mar 19, 2026
@LHT129 LHT129 force-pushed the 2026-03-13-修复-jsonwrapper-拷贝构造函数-ub branch from daf151b to 6b71791 Compare March 19, 2026 04:55
Copilot AI review requested due to automatic review settings March 19, 2026 04:55
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

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 addresses undefined behavior in vsag::JsonWrapper’s copy constructor to prevent crashes/memory corruption, and adds functional tests around copy behavior. It also includes refactors to backward-compatibility tooling (script + GitHub Actions workflow) that affect how compatibility checks and index artifacts are handled.

Changes:

  • Fix JsonWrapper copy constructor to avoid reading/deleting uninitialized members by properly initializing and allocating json_.
  • Add Catch2 functional tests covering copy-construction scenarios (including copying a non-owning wrapper).
  • Refactor compatibility check script and the “generate old version index” workflow upload logic.

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 5 comments.

File Description
src/json_wrapper.cpp Reworks copy constructor to eliminate UB and deep-copy JSON state safely.
tests/test_json_wrapper.cpp Adds functional tests validating copy constructor behavior across several JSON shapes/usages.
scripts/check_compatibility.sh Refactors iteration over old versions and simplifies output/flow for compatibility checks.
.github/workflows/generate_old_version_index.yml Simplifies release upload/create logic for compatibility index artifacts.

Comment on lines +56 to +58
auto w1 = vsag::JsonWrapper::Parse(R"({"valid": "json"})");
vsag::JsonWrapper sub = w1["valid"];
vsag::JsonWrapper w2 = sub;
Comment on lines +1 to +11
#!/usr/bin/bash

old_version_indexes="v0.17.2_hgraph v0.17.2_hnsw \
v0.16.14_hgraph v0.16.14_hnsw \
v0.15.1_hgraph v0.15.1_hnsw \
v0.14.8_hgraph v0.14.8_hnsw \
v0.13.4_hgraph v0.13.4_hnsw \
v0.13.0_hnsw \
v0.12.0_hnsw \
v0.11.14_hnsw \
v0.10.0_hnsw"
"v0.11.14_hnsw"
"v0.10.0_hnsw"
)
#!/usr/bin/bash
Comment on lines +16 to +17
./build-release/tools/check_compatibility/check_compatibility ${version}
if [ $? -ne 0 ]; then
Comment on lines +88 to +92
gh release upload compatibility-indexes $files --repo antgroup/vsag --clobber || \
gh release create compatibility-indexes $files \
--repo antgroup/vsag \
--title "VSAG Compatibility Test Indexes" \
--notes "Index files for backward compatibility testing"
@LHT129 LHT129 force-pushed the 2026-03-13-修复-jsonwrapper-拷贝构造函数-ub branch from 6b71791 to d45b470 Compare March 19, 2026 05:11
@pull-request-size pull-request-size bot added size/M and removed size/L labels Mar 19, 2026
Copilot AI review requested due to automatic review settings March 19, 2026 05:13
@LHT129 LHT129 force-pushed the 2026-03-13-修复-jsonwrapper-拷贝构造函数-ub branch from d45b470 to d22a1ec Compare March 19, 2026 05:13
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

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 undefined behavior in vsag::JsonWrapper’s copy constructor by removing pre-initialization cleanup and ensuring members are initialized before use, addressing crash/memory-corruption risk in JSON-heavy configuration paths.

Changes:

  • Reworked JsonWrapper copy constructor to avoid reading/deleting uninitialized members and to perform a safe deep copy.
  • Added functional tests covering multiple copy-constructor scenarios (empty, primitives, nested JSON, non-owning sub-wrapper).

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.

File Description
src/json_wrapper.cpp Fixes copy-constructor UB by initializing members and allocating/copying JSON safely.
tests/test_json_wrapper.cpp Adds tests intended to cover copy-constructor behavior across common and edge ownership cases.

Comment on lines +55 to +60
TEST_CASE("JsonWrapper Copy Non-owning Wrapper", "[ft][json_wrapper]") {
auto w1 = vsag::JsonWrapper::Parse(R"({"valid": "json"})");
vsag::JsonWrapper sub = w1["valid"];
vsag::JsonWrapper w2 = sub;
CHECK(w2.GetString() == "json");
}
Copilot AI review requested due to automatic review settings March 19, 2026 11:40
@LHT129 LHT129 force-pushed the 2026-03-13-修复-jsonwrapper-拷贝构造函数-ub branch from 65c1bd1 to efb583b Compare March 19, 2026 11:40
@LHT129
Copy link
Copy Markdown
Collaborator Author

LHT129 commented Mar 19, 2026

Review Response

I've addressed the key feedback:

  1. Non-owning wrapper copy test (lines 55-62): Updated the test to verify that the copied wrapper remains valid after the original owner w1 goes out of scope. This proves the copy constructor creates a true deep copy. Fixed in commit efb583b.

  2. Script changes (check_compatibility.sh, workflow): After rebasing to the latest main (which includes PR fix(ci): improve index upload logic to compatibility-indexes release #1708), these files no longer show changes in this PR's diff. The changes are correctly attributed to PR fix(ci): improve index upload logic to compatibility-indexes release #1708 which has already been merged.

The other Gemini/Copilot suggestions are optional improvements that don't affect correctness.

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Fixes undefined behavior in JsonWrapper’s copy constructor by removing cleanup of uninitialized members and changing the constructor to allocate/copy JSON safely; adds unit tests to cover copy scenarios.

Changes:

  • Reworked JsonWrapper copy constructor to avoid deleting uninitialized members and to allocate/copy nlohmann::json.
  • Added Catch2 tests covering copying empty, primitive, nested, and non-owning wrappers.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 4 comments.

File Description
src/json_wrapper.cpp Refactors copy ctor allocation/copy logic to avoid UB in member cleanup.
tests/test_json_wrapper.cpp Adds unit tests validating copy behavior and independence.

Comment on lines 33 to 39
JsonWrapper::JsonWrapper(const JsonWrapper& other) : owns_json_(true) {
if (other.json_ != nullptr) {
*json_ = *other.json_;
json_ = new nlohmann::json(*other.json_);
} else {
json_ = new nlohmann::json();
}
}
Comment on lines 33 to 39
JsonWrapper::JsonWrapper(const JsonWrapper& other) : owns_json_(true) {
if (other.json_ != nullptr) {
*json_ = *other.json_;
json_ = new nlohmann::json(*other.json_);
} else {
json_ = new nlohmann::json();
}
}
Comment on lines +56 to +64
std::string copied_value;
{
auto w1 = vsag::JsonWrapper::Parse(R"({"key": "value"})");
vsag::JsonWrapper sub = w1["key"];
vsag::JsonWrapper w2 = sub;
copied_value = w2.GetString();
}
CHECK(copied_value == "value");
}
// See the License for the specific language governing permissions and
// limitations under the License.

#include <catch2/catch_test_macros.hpp>
@LHT129 LHT129 force-pushed the 2026-03-13-修复-jsonwrapper-拷贝构造函数-ub branch from efb583b to 7ffbb69 Compare March 20, 2026 06:00
Copilot AI review requested due to automatic review settings March 25, 2026 09:42
@LHT129 LHT129 force-pushed the 2026-03-13-修复-jsonwrapper-拷贝构造函数-ub branch from 7ffbb69 to 70bc6d2 Compare March 25, 2026 09:42
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Fixes undefined behavior in JsonWrapper’s copy constructor that could lead to crashes/memory corruption by ensuring members are initialized correctly before use, and adds tests for copy scenarios.

Changes:

  • Replaced copy-constructor body with a member-initializer-list implementation (eliminates use of uninitialized members).
  • Removed invalid cleanup logic from the copy constructor.
  • Added Catch2 unit tests covering several copy scenarios.

Reviewed changes

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

File Description
tests/test_json_wrapper.cpp Adds unit tests intended to validate copy behavior (including nested and non-owning cases).
src/json_wrapper.cpp Rewrites copy constructor to allocate/initialize json_ safely and deep-copy from the source.

Comment on lines +57 to +62
vsag::JsonWrapper w2 = vsag::JsonWrapper();
{
auto w1 = vsag::JsonWrapper::Parse(R"({"key": "value"})");
vsag::JsonWrapper sub = w1["key"];
w2 = sub;
}
Copy link

Copilot AI Mar 25, 2026

Choose a reason for hiding this comment

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

The test name/intent suggests validating the copy constructor from a non-owning wrapper, but this code primarily exercises copy assignment (w2 = sub) and sub may be constructed via move/copy elision from w1["key"]. To ensure the copy constructor is actually tested, construct w2 via copy construction from an lvalue sub (e.g., declare sub in-scope, then vsag::JsonWrapper w2(sub); outside the inner scope), and avoid vsag::JsonWrapper w2 = vsag::JsonWrapper(); in favor of a plain default construction.

Suggested change
vsag::JsonWrapper w2 = vsag::JsonWrapper();
{
auto w1 = vsag::JsonWrapper::Parse(R"({"key": "value"})");
vsag::JsonWrapper sub = w1["key"];
w2 = sub;
}
vsag::JsonWrapper sub;
{
auto w1 = vsag::JsonWrapper::Parse(R"({"key": "value"})");
sub = w1["key"];
}
vsag::JsonWrapper w2(sub);

Copilot uses AI. Check for mistakes.
Comment on lines +34 to +35
: json_(other.json_ ? new nlohmann::json(*other.json_) : new nlohmann::json()),
owns_json_(true) {
Copy link

Copilot AI Mar 25, 2026

Choose a reason for hiding this comment

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

The conditional allocation duplicates the new nlohmann::json(...) expression, which makes the initializer harder to read/modify. Consider allocating once (e.g., initialize json_ to a fresh nlohmann::json and conditionally copy into it in the constructor body), which keeps the ownership semantics the same while reducing duplication.

Suggested change
: json_(other.json_ ? new nlohmann::json(*other.json_) : new nlohmann::json()),
owns_json_(true) {
: json_(new nlohmann::json()),
owns_json_(true) {
if (other.json_ != nullptr) {
*json_ = *other.json_;
}

Copilot uses AI. Check for mistakes.
@LHT129 LHT129 force-pushed the 2026-03-13-修复-jsonwrapper-拷贝构造函数-ub branch from 70bc6d2 to b20a3f7 Compare March 26, 2026 06:59
Fix UB in JsonWrapper copy constructor where uninitialized members
were checked before initialization. Use member initializer list
and explicit nullptr comparison.

Fixes antgroup#1714

Signed-off-by: LHT129 <tianlan.lht@antgroup.com>
@LHT129 LHT129 force-pushed the 2026-03-13-修复-jsonwrapper-拷贝构造函数-ub branch from b20a3f7 to 19a1789 Compare March 26, 2026 08:25
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug] Fix undefined behavior in JsonWrapper copy constructor

2 participants