Please find test snippet and patch attached.

[[[
Fix JavaHL crash in TunnelAgent.CloseTunnelCallback after GC

When jobject reference is kept across different JNI calls, a new global
reference must be requested with NewGlobalRef(). Otherwise, GC is free
to remove the object. Even if Java code keeps a reference to the object,
GC can still move the object around, invalidating the kept jobject,
which results in a native crash when trying to access it.

[in subversion/bindings/javahl]
* native/OperationContext.cpp
  (OperationContext::openTunnel): Add NewGlobalRef() for kept jobject.
  (OperationContext::closeTunnel): Add a matching DeleteGlobalRef().
]]]
Index: subversion/bindings/javahl/native/OperationContext.cpp
===================================================================
--- subversion/bindings/javahl/native/OperationContext.cpp      (revision 
1880687)
+++ subversion/bindings/javahl/native/OperationContext.cpp      (working copy)
@@ -629,6 +629,12 @@
           jtunnel_name, juser, jhostname, jint(port)),
       SVN_ERR_BASE);
 
+  if (tc->jclosecb)
+    {
+      tc->jclosecb = env->NewGlobalRef(tc->jclosecb);
+      SVN_JNI_CATCH(, SVN_ERR_BASE);
+    }
+
   return SVN_NO_ERROR;
 }
 
@@ -656,4 +662,5 @@
       SVN_JNI_CATCH_VOID(mid = env->GetMethodID(cls, "closeTunnel", "()V"));
     }
   SVN_JNI_CATCH_VOID(env->CallVoidMethod(jclosecb, mid));
+  env->DeleteGlobalRef(jclosecb);
 }
import java.io.*;
import java.nio.*;
import java.nio.channels.*;
import java.nio.charset.*;

import org.apache.subversion.javahl.*;
import org.apache.subversion.javahl.callback.*;
import org.apache.subversion.javahl.remote.*;

/**
 * @author Marc Strapetz
 */
public class JavaHL_Crash_RemoteSession_nativeDispose {
        public static void main(String[] args) throws Exception {
                final SVNClient svnClient = new SVNClient();
                final TunnelAgent.CloseTunnelCallback closeTunnelCallback = () 
-> System.out.println("TunnelAgent.CloseTunnelCallback");

                final TunnelAgent tunnelAgent = new TunnelAgent() {
                        @Override
                        public boolean checkTunnel(String name) {
                                return true;
                        }

                        @Override
                        public CloseTunnelCallback 
openTunnel(ReadableByteChannel request, WritableByteChannel response, String 
name, String user, String hostname, int port) throws Throwable {
                                new Thread(() -> {
                                        try {
                                                sleep(100);
                                                
response.write(ByteBuffer.wrap("( success ( 2 2 ( ) ( edit-pipeline svndiff1 
absent-entries commit-revprops depth log-revprops atomic-revprops 
partial-replay inherited-props ephemeral-txnprops file-revs-reverse ) ) ) 
".getBytes(StandardCharsets.UTF_8)));
                                                
request.read(ByteBuffer.allocate(1024));
                                                sleep(100);
                                                
response.write(ByteBuffer.wrap("( success ( ( ANONYMOUS EXTERNAL ) 15:Test 
Repository ) ) ".getBytes(StandardCharsets.UTF_8)));
                                                
request.read(ByteBuffer.allocate(1024));
                                                sleep(100);
                                                
response.write(ByteBuffer.wrap("( success ( ) ) ( success ( 
36:00000000-0000-0000-0000-000000000000 
57:svn+ssh://testu...@domain.com/home/testUser/svnrepos/test ( mergeinfo ) ) ) 
".getBytes(StandardCharsets.UTF_8)));
                                                final ByteBuffer allocate = 
ByteBuffer.allocate(1024);
                                                request.read(allocate);
                                                sleep(100);
                                        }
                                        catch (IOException e) {
                                                e.printStackTrace();
                                        }
                                }).start();

                                return closeTunnelCallback;
                        }
                };
                svnClient.setTunnelAgent(tunnelAgent);

                try {
                        final RemoteFactory f = new RemoteFactory();
                        f.setTunnelAgent(tunnelAgent);

                        final ISVNRemote remote = 
f.openRemoteSession("svn+ssh://testu...@domain.com/home/testUser/svnrepos/test",
 4);

                        // 'OperationContext::openTunnel()' doesn't 
'NewGlobalRef()' callback returned by 'TunnelAgent.openTunnel()'.
                        // When GC runs, it disposes the callback. When JavaHL 
tries to call it in 'remote.dispose()', JVM crashes.
                        for (int index = 0; index < 5; index++) {
                                sleep(100);
                                System.gc();
                        }

                        remote.dispose();
                }
                catch (SubversionException ex) {
                        ex.printStackTrace();
                }
        }

        private static void sleep(int millis) {
                try {
                        Thread.sleep(millis);
                }
                catch (InterruptedException e) {
                        e.printStackTrace();
                }
        }
}

Reply via email to