javacpp icon indicating copy to clipboard operation
javacpp copied to clipboard

JVM crashes when indexing into reference to vector

Open codeinred opened this issue 1 year ago • 4 comments

Hello,

We've observed an issue wherein a reference to an internal member variable appears to become dangling after the class that contains that member is garbage-collected.

Question

Can references returned by @ByRef-annotated functions become dangling? Is there a way to prevent the reference from becoming dangling, or to instruct javacpp to make a copy of the underlying object?

GetNumbers obj = makeGetNumbers( 1000 );

VecVecR values = obj.nums(); /// If obj is garbage-collected, does this reference become dangling?

Here, VecVecR and GetNumbers are wrapped types. obj.nums() would return a const reference in C++. The java declaration of nums() is annotated with @ByRef. Full definitions are provided below. On the C++ side of things, GetNumbers is held by a unique_ptr to GetNumbersI.

Definitions

Here, GetNumbers is a wrapper for a C++ class, where the nums() function returns a const reference. This reference points to an internal member variable.

Is it possible for an instance of GetNumbers to be garbage-collected while another variable holds onto the object returned by nums()?

We have a class with a method that returns a vector by const reference:

#pragma once
#include <memory>
#include <vector>


namespace example {
    using R       = double;
    using VecR    = std::vector<R>;
    using VecVecR = std::vector<VecR>;

    struct GetNumbersI
    {
        virtual VecVecR const& nums() const = 0;
    };

    using GetNumbersU = std::unique_ptr<GetNumbersI const>;
    GetNumbersU makeGetNumbers( size_t size );
} // namespace example

Source file:

#include "MyClass.h"

namespace example {
    struct GetNumbers : GetNumbersI
    {
        int     a;
        VecVecR values_;
        int     b;

        GetNumbers( VecVecR values )
        : a{}
        , values_( std::move( values ) )
        , b{}
        {
        }
        virtual VecVecR const& nums() const { return values_; }
    };

    GetNumbersU makeGetNumbers( size_t size )
    {
        return std::make_unique<GetNumbers>( VecVecR( size, VecR( size, 0.0 ) ) );
    }

} // namespace example

This produces the following class:

// Targeted by JavaCPP version 1.5.8: DO NOT EDIT THIS FILE

package com.voladynamics.example;

import java.nio.*;
import org.bytedeco.javacpp.*;
import org.bytedeco.javacpp.annotation.*;

import static com.voladynamics.example.global.example.*;


    @Name("example::GetNumbersI") @Properties(inherit = com.voladynamics.presets.example.class)
public class GetNumbers extends Pointer {
        static { Loader.load(); }
        /** Pointer cast constructor. Invokes {@link Pointer#Pointer(Pointer)}. */
        public GetNumbers(Pointer p) { super(p); }
    
        public native @Const @ByRef VecVecR nums();
    }

Sample code causing crash

Here is roughly the code that causes the issue:

GetNumbers obj = makeGetNumbers( 1000 );

VecVecR values = obj.nums(); 

/// Some expensive computation, that does not directly reference `obj`
/// It appears that `obj` gets garbage-collected during this time period

/// This code will cause the JVM to crash for newer JVM versions (eg, OpenJDK 17 or 19)
double sum = 0.0;
for (int it = 0; it < 100; it++) {
    for (int i = 0; i < values.size(); i++) {
        for (int j = 0; j < values.get(i).size(); j++) {
            sum += values.get(i).get(j);
        }
    }
}

When a line of code that references obj is appended to the above, the crash goes away:

GetNumbers obj = makeGetNumbers( 1000 );

VecVecR values = obj.nums(); 

/// Some expensive computation, that does not directly reference `obj`
/// It appears that `obj` gets garbage-collected during this time period, if it's not referenced anywhere else in the function

/// This code will cause the JVM to crash for newer JVM versions (eg, OpenJDK 17 or 19)
double sum = 0.0;
for (int it = 0; it < 100; it++) {
    for (int i = 0; i < values.size(); i++) {
        for (int j = 0; j < values.get(i).size(); j++) {
            sum += values.get(i).get(j);
        }
    }
}

/// If this line is uncommented, obj won't be garbage-collected
/// System.out.println( obj.nums().get(0).get(0) );

codeinred avatar Apr 10 '23 22:04 codeinred