Aubin
Aubin

Reputation: 14863

JNI allocation corrupt heap

Then following code has an unexpected behavior: the File argument becomes, at runtime, a DirectoryListingPerfTest.

I suppose the java heap is corrupted and my native code doesn't call it with the correct arguments.

Here is the Java Code:

public class DirectoryListingPerfTest implements FileFilter {

   private final SortedSet<File> _dirs = new TreeSet<>();

   @SuppressWarnings("cast")
   @Override
   public boolean accept( File pathname ) {
      if( pathname instanceof File ) {
        _dirs.add( pathname );
      }
      else {
         System.err.println( pathname.getClass());
      }
      return false;
   }

   static native void find( String path, FileFilter listener );

   public static void main( String[] args ) {
      System.loadLibrary( "fs_DirectoryListingPerfTest" );
      final DirectoryListingPerfTest ff = new DirectoryListingPerfTest();
      long atStart, elapsed;
      atStart = System.nanoTime();
      find( "/usr", ff );
      elapsed = System.nanoTime() - atStart;
      System.err.printf( "native(1): %,9.2f ms\n", elapsed / 1.0E+06 );
      atStart = System.nanoTime();
      find( "/usr", ff );
      elapsed = System.nanoTime() - atStart;
      System.err.printf( "native(2): %,9.2f ms\n", elapsed / 1.0E+06 );
   }
}

Here is the C++ JNI code:

static jclass    File;
static jmethodID FileCtor;

static void Java_fs_DirectoryListingPerfTest_find_r(
   JNIEnv *     env,
   const char * path,
   jobject      listener,
   jmethodID    accept )
{
   jstring jPath = env->NewStringUTF( path );
   jobject file  = env->NewObject( File, FileCtor, jPath );
   jobject gFile = env->NewGlobalRef( file );
   env->CallBooleanMethod( listener, accept, gFile );
   DIR * dir = opendir( path );
   if( dir ) {
      struct dirent * entry;
      while(( entry = readdir( dir )) != NULL ) {
         if(  ( 0 == strcmp( entry->d_name, "."  ))
            ||( 0 == strcmp( entry->d_name, ".." )))
         {
            continue;
         }
         if( entry->d_type == DT_DIR ) {
            char * buffer = (char *)malloc(
               strlen( path ) + 1 + strlen( entry->d_name ) + 1 );
            strcpy( buffer, path );
            strcat( buffer, "/" );
            strcat( buffer, entry->d_name );
            Java_fs_DirectoryListingPerfTest_find_r(
               env, buffer, listener, accept );
            free( buffer );
         }
      }
      closedir( dir );
   }
}

JNIEXPORT void JNICALL Java_fs_DirectoryListingPerfTest_find(
   JNIEnv * env,
   jclass   clazz,
   jstring  jpath,
   jobject  listener )
{
   if( File == NULL ) {
      File     = env->FindClass( "java/io/File" );
      FileCtor = env->GetMethodID( File, "<init>", "(Ljava/lang/String;)V");
   }
   jclass FileFilter = env->GetObjectClass( listener );
   jmethodID accept =
      env->GetMethodID( FileFilter, "accept", "(Ljava/io/File;)Z");
   const char * path       = env->GetStringUTFChars( jpath, JNI_FALSE );
   Java_fs_DirectoryListingPerfTest_find_r( env, path, listener, accept );
   env->ReleaseStringUTFChars( jpath, path );
}

Here is the execution trace:

native(1):    584,76 ms
class fs.DirectoryListingPerfTest
class fs.DirectoryListingPerfTest
... (a lot !)
class fs.DirectoryListingPerfTest
Exception in thread "main" java.lang.ClassCastException: fs.DirectoryListingPerfTest cannot be cast to java.lang.Comparable
    at java.util.TreeMap.put(TreeMap.java:565)
    at java.util.TreeSet.add(TreeSet.java:255)
    at fs.DirectoryListingPerfTest.accept(DirectoryListingPerfTest.java:16)
    at fs.DirectoryListingPerfTest.find(Native Method)
    at fs.DirectoryListingPerfTest.main(DirectoryListingPerfTest.java:91)

Upvotes: 1

Views: 422

Answers (2)

Aubin
Aubin

Reputation: 14863

Thanks Andrew, from your advices, I have made ant tested a corrected C++ version, hope this helps:

namespace fs {

   class DirectoryListingPerfTest {
   private:

      JNIEnv *  _env;
      jclass    _File;
      jmethodID _FileCtor;
      jobject   _listener;
      jmethodID _accept;

   public:

      DirectoryListingPerfTest(
         JNIEnv *  env,
         jclass    File,
         jmethodID FileCtor,
         jobject   listener,
         jmethodID accept    )
      :
         _env     ( env ),
         _File    ( File ),
         _FileCtor( FileCtor ),
         _listener( listener ),
         _accept  ( accept )
      {}

      void find( const char * path ) {
         jstring jPath = _env->NewStringUTF( path );
         jobject file  = _env->NewObject( _File, _FileCtor, jPath );
         jobject gFile = _env->NewGlobalRef( file );
         _env->CallBooleanMethod( _listener, _accept, gFile );
         DIR * dir = opendir( path );
         if( dir ) {
            struct dirent * entry;
            while(( entry = readdir( dir )) != NULL ) {
               if(  ( 0 == strcmp( entry->d_name, "."  ))
                  ||( 0 == strcmp( entry->d_name, ".." )))
               {
                  continue;
               }
               if( entry->d_type == DT_DIR ) {
                  char * buffer = (char *)malloc(
                     strlen( path ) + 1 + strlen( entry->d_name ) + 1 );
                  strcpy( buffer, path );
                  strcat( buffer, "/" );
                  strcat( buffer, entry->d_name );
                  find( buffer );
                  free( buffer );
               }
            }
            closedir( dir );
         }
      }

   private:
      DirectoryListingPerfTest( const DirectoryListingPerfTest & );
      DirectoryListingPerfTest & operator = ( const DirectoryListingPerfTest & );
   };
}

JNIEXPORT void JNICALL Java_fs_DirectoryListingPerfTest_find(
   JNIEnv * env,
   jclass   clazz,
   jstring  jpath,
   jobject  listener )
{
   jclass       File       = env->FindClass( "java/io/File" );
   jmethodID    FileCtor   = env->GetMethodID( File, "<init>", "(Ljava/lang/String;)V");
   jclass       FileFilter = env->GetObjectClass( listener );
   jmethodID    accept     = env->GetMethodID( FileFilter, "accept", "(Ljava/io/File;)Z");
   const char * path       = env->GetStringUTFChars( jpath, JNI_FALSE );
   fs::DirectoryListingPerfTest( env, File, FileCtor, listener, accept ).find( path );
   env->ReleaseStringUTFChars( jpath, path );
}

Upvotes: 0

Andrew Henle
Andrew Henle

Reputation: 1

You can not cache Java objects between calls. This code is wrong:

static jclass    File;
static jmethodID FileCtor;

...

   if( File == NULL ) {
      File     = env->FindClass( "java/io/File" );
      FileCtor = env->GetMethodID( File, "<init>", "(Ljava/lang/String;)V");
   }

If you want to cache Java values, in general you need to create a global reference. See:

Upvotes: 2

Related Questions