bytedeco/javacpp

javacpp-generated `pop_back()` returns a `@ByRef` alias to the element it just destroyed

开放

#830 创建于 2026年7月30日

 (1 条评论) (0 个反应) (0 位负责人)Java (620 个派生)batch import
bughelp wanted

仓库指标

星标
 (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.

dangling-popback-reproduction.zip

贡献者指南