bytedeco/javacpp

JVM crashes when indexing into reference to vector

Open

#672 opened on Apr 10, 2023

View on GitHub
 (4 comments) (0 reactions) (0 assignees)Java (620 forks)batch import
enhancementhelp wantedquestion

Repository metrics

Stars
 (4,279 stars)
PR merge metrics
 (No merged PRs in 30d)

Description

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) );

Contributor guide