javacpp-generated `pop_back()` returns a `@ByRef` alias to the element it just destroyed
#830 创建于 2026年7月30日
仓库指标
- 星标
- (4,279 个星标)
- PR 合并指标
- (30 天内没有已合并 PR)
描述
The pop_back() for vector and deque as generated by javacpp always returns a dangling
reference for anything other than a primitive type.
public Widget pop_back() {
long size = size();
Widget value = get(size - 1); // @ByRef alias to &vec[size-1]
resize(size - 1); // destroys vec[size-1] -> ~Widget() runs
return value; // dangling: element already destroyed
}
For a T with a non-trivial destructor (e.g. PIMPL freeing a unique_ptr), the returned
peer is dangling the instant pop_back() returns. Any use is a use-after-free. (The accessors
get(i)/front()/back() are fine: their alias is valid at return and only dangles on
later mutation, per #672. pop_back() is the only method that mutates its own referent
before returning it.)
Reproduce (see attached jcpp-popback-repro/)
Widget owns a heap pointer; its destructor frees and nulls it. Any call on a widget after
that is a use-after-free. In the reproduction, ~Widget() also sets the handle
it holds to null so that any additional calls result in a deterministic SIGSEGV.
VecWidget v = new VecWidget();
v.push_back(new Widget(10)); v.push_back(new Widget(20)); v.push_back(new Widget(30));
v.back().value(); // 30 — alias into a live element is fine
v.pop_back().value(); // SIGSEGV in Java_..._Widget_value: reads a destroyed Widget
# SIGSEGV (0xb) ...
# C [libjniwidget.dylib+...] Java_com_example_demo_Widget_value+...
Reproduced on macOS arm64 / Zulu OpenJDK 21 / Apple clang 17, JavaCPP 1.5.13.
Suggested fix: pop-and-move-out
Bind a native pop_back to a small free function that moves the element out of the
vector by value, and let the existing @ByVal-return path give it an owned peer:
// generated Java
public T pop_back() { return popBack(this); }
@Namespace("…") @Name("JavaCPP_popBack") private static native @ByVal T popBack(@ByRef VecT v);
// emitted once alongside the container (T = element type)
static T JavaCPP_popBack(std::vector<T>& v) { T value = std::move(v.back()); v.pop_back(); return value; }
The result is @ByVal so that it never aliases vector storage; pop_back() then
destroys only the moved-from husk.
I verified the move-out approach end to end against the attached repro: the same read that
SIGSEGVs through the generated pop_back() returns the correct owned value with no crash
(see MainFixed.java / demo::popBackOut).
Let me know if you would like me to create a patch.