Skip to content

Conversation

Copy link

Copilot AI commented Jan 22, 2026

Issue #308 reported that moving an external_pointer clears its attributes. The fix was already applied (changing move operations to transfer data_ directly instead of calling reset(rhs.release())), but lacked test coverage and had a critical bug.

Changes

  • Added test case for attribute preservation during move operations (both move constructor and move assignment operator)
  • Fixed missing return statement in external_pointer::operator=(external_pointer&&) - the operator was declared to return external_pointer& but had no return, causing undefined behavior

Test Example

// Move constructor preserves attributes
cpp11::external_pointer<int> p(new int(42));
Rf_setAttrib(p, R_ClassSymbol, Rf_mkString("test_class"));

cpp11::external_pointer<int> p_moved = std::move(p);
// Attribute is preserved on p_moved

// Move assignment operator preserves attributes  
cpp11::external_pointer<int> p1(new int(1));
Rf_setAttrib(p1, R_ClassSymbol, Rf_mkString("test_class"));

cpp11::external_pointer<int> p2(nullptr);
p2 = std::move(p1);
// Attribute is preserved on p2

✨ Let Copilot coding agent set things up for you — coding agent works faster and does higher quality work when set up for your repo.

Copilot AI and others added 5 commits January 22, 2026 06:00
…ssue #308)

Co-authored-by: krlmlr <1741643+krlmlr@users.noreply.github.com>
Co-authored-by: krlmlr <1741643+krlmlr@users.noreply.github.com>
Co-authored-by: krlmlr <1741643+krlmlr@users.noreply.github.com>
Co-authored-by: krlmlr <1741643+krlmlr@users.noreply.github.com>
Co-authored-by: krlmlr <1741643+krlmlr@users.noreply.github.com>
Copilot AI changed the title [WIP] Add a test for premature release of external pointers Add test for external_pointer attribute preservation and fix move assignment operator Jan 22, 2026
Copilot AI requested a review from krlmlr January 22, 2026 06:04
@krlmlr krlmlr marked this pull request as ready for review January 22, 2026 06:05
Copilot AI review requested due to automatic review settings January 22, 2026 06:05
@krlmlr krlmlr merged commit 51908e8 into b-extptr-leak Jan 22, 2026
2 of 3 checks passed
@krlmlr krlmlr deleted the copilot/sub-pr-423 branch January 22, 2026 06:05
Copy link

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 a critical bug in the external_pointer move assignment operator and adds comprehensive test coverage for attribute preservation during move operations, addressing issue #308.

Changes:

  • Added missing return statement in external_pointer::operator=(external_pointer&&) to fix undefined behavior
  • Added test case verifying attributes are preserved during both move construction and move assignment
  • Added CodeQL configuration file

Reviewed changes

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

File Description
inst/include/cpp11/external_pointer.hpp Fixed move assignment operator by adding missing return statement
cpp11test/src/test-external_pointer.cpp Added comprehensive test for attribute preservation in move operations
_codeql_detected_source_root Added CodeQL source root configuration

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

expect_true(strcmp(CHAR(STRING_ELT(class_attr_after, 0)), "test_class") == 0);

// Clean up
delete p_moved.release();
Copy link

Copilot AI Jan 22, 2026

Choose a reason for hiding this comment

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

Calling delete on the result of release() will attempt to delete the memory that was already wrapped by the external pointer, but the external pointer's finalizer has already been set up to delete this memory when the R object is garbage collected. This creates a double-free scenario. Since the external pointer owns the memory, simply allowing p_moved to go out of scope is sufficient - the finalizer will handle cleanup.

Copilot uses AI. Check for mistakes.
expect_true(strcmp(CHAR(STRING_ELT(class_attr_after, 0)), "test_class") == 0);

// Clean up
delete p2.release();
Copy link

Copilot AI Jan 22, 2026

Choose a reason for hiding this comment

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

Same double-free issue as above. The external pointer's finalizer will delete the memory when the R object is garbage collected. Calling delete on release() creates a double-free scenario. Remove this manual deletion and let the external pointer's destructor handle cleanup.

Copilot uses AI. Check for mistakes.
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.

2 participants