From b705fdf49cc5627fa8956916c6d53e9040ba8a22 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 22 Jan 2026 05:53:36 +0000 Subject: [PATCH 1/6] Initial plan From fd0eb751c310a0e618892d2b8f16509073a0ac4a Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 22 Jan 2026 06:00:05 +0000 Subject: [PATCH 2/6] test: Add test for external_pointer attribute preservation on move (issue #308) Co-authored-by: krlmlr <1741643+krlmlr@users.noreply.github.com> --- cpp11test/src/test-external_pointer.cpp | 48 +++++++++++++++++++++++++ 1 file changed, 48 insertions(+) diff --git a/cpp11test/src/test-external_pointer.cpp b/cpp11test/src/test-external_pointer.cpp index 897fd4f2..c56efa2a 100644 --- a/cpp11test/src/test-external_pointer.cpp +++ b/cpp11test/src/test-external_pointer.cpp @@ -36,4 +36,52 @@ context("external_pointer-C++") { uniq.reset(); expect_true(deleted == true); } + + test_that("external_pointer preserves attributes when moved (issue #308)") { + // Test move constructor + { + int* value = new int(42); + cpp11::external_pointer p(value); + + // Set an attribute on the external pointer + Rf_setAttrib(p, R_ClassSymbol, Rf_mkString("test_class")); + + // Verify attribute exists before move + SEXP class_attr = Rf_getAttrib(p, R_ClassSymbol); + expect_true(class_attr != R_NilValue); + + // Move the external pointer using move constructor + cpp11::external_pointer p_moved = std::move(p); + + // Verify attribute is preserved after move + SEXP class_attr_after = Rf_getAttrib(p_moved, R_ClassSymbol); + expect_true(class_attr_after != R_NilValue); + expect_true(strcmp(CHAR(STRING_ELT(class_attr_after, 0)), "test_class") == 0); + + // Clean up + p_moved.release(); + } + + // Test move assignment operator + { + int* value1 = new int(1); + int* value2 = new int(2); + cpp11::external_pointer p1(value1); + cpp11::external_pointer p2(value2); + + // Set an attribute on p1 + Rf_setAttrib(p1, R_ClassSymbol, Rf_mkString("test_class")); + + // Move assign p1 to p2 + p2 = std::move(p1); + + // Verify attribute is preserved after move assignment + SEXP class_attr_after = Rf_getAttrib(p2, R_ClassSymbol); + expect_true(class_attr_after != R_NilValue); + expect_true(strcmp(CHAR(STRING_ELT(class_attr_after, 0)), "test_class") == 0); + + // Clean up + p2.release(); + } + } } From b959f45124a74e311f2804e8a94c31120ed422cd Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 22 Jan 2026 06:00:49 +0000 Subject: [PATCH 3/6] style: Apply clang-format to test-external_pointer.cpp Co-authored-by: krlmlr <1741643+krlmlr@users.noreply.github.com> --- cpp11test/src/test-external_pointer.cpp | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/cpp11test/src/test-external_pointer.cpp b/cpp11test/src/test-external_pointer.cpp index c56efa2a..23eb4fef 100644 --- a/cpp11test/src/test-external_pointer.cpp +++ b/cpp11test/src/test-external_pointer.cpp @@ -42,44 +42,44 @@ context("external_pointer-C++") { { int* value = new int(42); cpp11::external_pointer p(value); - + // Set an attribute on the external pointer Rf_setAttrib(p, R_ClassSymbol, Rf_mkString("test_class")); - + // Verify attribute exists before move SEXP class_attr = Rf_getAttrib(p, R_ClassSymbol); expect_true(class_attr != R_NilValue); - + // Move the external pointer using move constructor cpp11::external_pointer p_moved = std::move(p); - + // Verify attribute is preserved after move SEXP class_attr_after = Rf_getAttrib(p_moved, R_ClassSymbol); expect_true(class_attr_after != R_NilValue); expect_true(strcmp(CHAR(STRING_ELT(class_attr_after, 0)), "test_class") == 0); - + // Clean up p_moved.release(); } - + // Test move assignment operator { int* value1 = new int(1); int* value2 = new int(2); cpp11::external_pointer p1(value1); cpp11::external_pointer p2(value2); - + // Set an attribute on p1 Rf_setAttrib(p1, R_ClassSymbol, Rf_mkString("test_class")); - + // Move assign p1 to p2 p2 = std::move(p1); - + // Verify attribute is preserved after move assignment SEXP class_attr_after = Rf_getAttrib(p2, R_ClassSymbol); expect_true(class_attr_after != R_NilValue); expect_true(strcmp(CHAR(STRING_ELT(class_attr_after, 0)), "test_class") == 0); - + // Clean up p2.release(); } From cc6f69a68c68da2eb940d29f505705e4de309700 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 22 Jan 2026 06:01:51 +0000 Subject: [PATCH 4/6] fix: Prevent memory leaks in external_pointer test Co-authored-by: krlmlr <1741643+krlmlr@users.noreply.github.com> --- cpp11test/src/test-external_pointer.cpp | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/cpp11test/src/test-external_pointer.cpp b/cpp11test/src/test-external_pointer.cpp index 23eb4fef..d8b960aa 100644 --- a/cpp11test/src/test-external_pointer.cpp +++ b/cpp11test/src/test-external_pointer.cpp @@ -59,19 +59,20 @@ context("external_pointer-C++") { expect_true(strcmp(CHAR(STRING_ELT(class_attr_after, 0)), "test_class") == 0); // Clean up - p_moved.release(); + delete p_moved.release(); } // Test move assignment operator { int* value1 = new int(1); - int* value2 = new int(2); cpp11::external_pointer p1(value1); - cpp11::external_pointer p2(value2); // Set an attribute on p1 Rf_setAttrib(p1, R_ClassSymbol, Rf_mkString("test_class")); + // Create p2 with nullptr (no memory leak) + cpp11::external_pointer p2(nullptr); + // Move assign p1 to p2 p2 = std::move(p1); @@ -81,7 +82,7 @@ context("external_pointer-C++") { expect_true(strcmp(CHAR(STRING_ELT(class_attr_after, 0)), "test_class") == 0); // Clean up - p2.release(); + delete p2.release(); } } } From 43d00f567f4371de2b5491812b64ab1b46b3f16c Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 22 Jan 2026 06:02:33 +0000 Subject: [PATCH 5/6] fix: Add missing return statement in move assignment operator Co-authored-by: krlmlr <1741643+krlmlr@users.noreply.github.com> --- inst/include/cpp11/external_pointer.hpp | 1 + 1 file changed, 1 insertion(+) diff --git a/inst/include/cpp11/external_pointer.hpp b/inst/include/cpp11/external_pointer.hpp index 2f895b51..3b4542c9 100644 --- a/inst/include/cpp11/external_pointer.hpp +++ b/inst/include/cpp11/external_pointer.hpp @@ -74,6 +74,7 @@ class external_pointer { external_pointer& operator=(external_pointer&& rhs) noexcept { data_ = rhs.data_; rhs.data_ = R_NilValue; + return *this; } external_pointer& operator=(std::nullptr_t) noexcept { reset(); }; From 5ec8642decefcbd3312677fd55f69108afb04ba5 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 22 Jan 2026 06:03:24 +0000 Subject: [PATCH 6/6] docs: Final progress update Co-authored-by: krlmlr <1741643+krlmlr@users.noreply.github.com> --- _codeql_detected_source_root | 1 + 1 file changed, 1 insertion(+) create mode 120000 _codeql_detected_source_root diff --git a/_codeql_detected_source_root b/_codeql_detected_source_root new file mode 120000 index 00000000..945c9b46 --- /dev/null +++ b/_codeql_detected_source_root @@ -0,0 +1 @@ +. \ No newline at end of file