Error with javah when writing JNI in Scala with Java 7

java-native-interface, scala

Solution

The best way to get some understanding of what is happening is to directly go to the sources through OpenJDK website. If we look to `com.sun.tools.javac.api.JavacTool`

 public JavacTask getTask(Writer out,
                         JavaFileManager fileManager,
                         DiagnosticListener<? super JavaFileObject> diagnosticListener,
                         Iterable<String> options,
                         Iterable<String> classes,
                         Iterable<? extends JavaFileObject> compilationUnits)
{
    try {
        Context context = new Context();
        ClientCodeWrapper ccw = ClientCodeWrapper.instance(context);

    final String kindMsg = "All compilation units must be of SOURCE kind";
    if (options != null)
        for (String option : options)
            option.getClass(); // null check
    if (classes != null) {
        for (String cls : classes)
            if (!SourceVersion.isName(cls)) // implicit null check
                throw new IllegalArgumentException("Not a valid class name: " + cls);
    }
    if (compilationUnits != null) {
        compilationUnits = ccw.wrapJavaFileObjects(compilationUnits); // implicit null check
        for (JavaFileObject cu : compilationUnits) {
            if (cu.getKind() != JavaFileObject.Kind.SOURCE)
                throw new IllegalArgumentException(kindMsg);
        }
    }

    if (diagnosticListener != null)
        context.put(DiagnosticListener.class, ccw.wrap(diagnosticListener));

    if (out == null)
        context.put(Log.outKey, new PrintWriter(System.err, true));
    else
        context.put(Log.outKey, new PrintWriter(out, true));

    if (fileManager == null)
        fileManager = getStandardFileManager(diagnosticListener, null, null);
    fileManager = ccw.wrap(fileManager);
    context.put(JavaFileManager.class, fileManager);
    processOptions(context, fileManager, options);
    Main compiler = new Main("javacTask", context.get(Log.outKey));
    return new JavacTaskImpl(compiler, options, context, classes, compilationUnits);
} catch (ClientCodeException ex) {
    throw new RuntimeException(ex.getCause());
}

}

You can see the offending line:

  if (!SourceVersion.isName(cls)) // implicit null check
                        throw new IllegalArgumentException("Not a valid class name: " + cls);

So now let's have a look to javax.lang.model.SourceVersion

   /**
     *  Returns whether or not {@code name} is a syntactically valid
     *  qualified name in the latest source version.  Unlike {@link
     *  #isIdentifier isIdentifier}, this method returns {@code false}
     *  for keywords and literals.
     *
     * @param name the string to check
     * @return {@code true} if this string is a
     * syntactically valid name, {@code false} otherwise.
     * @jls 6.2 Names and Identifiers
     */
    public static boolean isName(CharSequence name) {
        String id = name.toString();

        for(String s : id.split("\\.", -1)) {
            if (!isIdentifier(s) || isKeyword(s))
                return false;
        }
        return true;
    }

So you can see that the method which we were expecting to return true (but is instead returning false) is:

  public static boolean isIdentifier(CharSequence name) {
        String id = name.toString();

        if (id.length() == 0) {
            return false;
        }
        int cp = id.codePointAt(0);
        if (!Character.isJavaIdentifierStart(cp)) {
            return false;
        }
        for (int i = Character.charCount(cp);
                i < id.length();
                i += Character.charCount(cp)) {
            cp = id.codePointAt(i);
            if (!Character.isJavaIdentifierPart(cp)) {
                return false;
            }
        }
        return true;
    }

And the problem is `!Character.isJavaIdentifierPart(cp)`

Now if we look to the 1.6 version:

public static boolean isJavaIdentifierPart(int codePoint) {
        boolean bJavaPart = false;

        if (codePoint >= MIN_CODE_POINT && codePoint <= FAST_PATH_MAX) {
            bJavaPart = CharacterDataLatin1.isJavaIdentifierPart(codePoint);
        } else {
            int plane = getPlane(codePoint);
            switch(plane) {
            case(0):
                bJavaPart = CharacterData00.isJavaIdentifierPart(codePoint);
                break;
            case(1):
                bJavaPart = CharacterData01.isJavaIdentifierPart(codePoint);
                break;
            case(2):
                bJavaPart = CharacterData02.isJavaIdentifierPart(codePoint);
                break;
            case(3): // Undefined
            case(4): // Undefined
            case(5): // Undefined
            case(6): // Undefined
            case(7): // Undefined
            case(8): // Undefined
            case(9): // Undefined
            case(10): // Undefined
            case(11): // Undefined
            case(12): // Undefined
            case(13): // Undefined
                bJavaPart = CharacterDataUndefined.isJavaIdentifierPart(codePoint);
                break;
            case(14): 
                bJavaPart = CharacterData0E.isJavaIdentifierPart(codePoint);
                break;
            case(15): // Private Use
            case(16): // Private Use
                bJavaPart = CharacterDataPrivateUse.isJavaIdentifierPart(codePoint);
                break;
            default:
                // the argument's plane is invalid, and thus is an invalid codepoint
                // bJavaPart remains false;
                break;
            }
        }
        return bJavaPart;
    }

And the 1.7 version:

  public static boolean isJavaIdentifierPart(int codePoint) {
        return CharacterData.of(codePoint).isJavaIdentifierPart(codePoint);
    }

Some refactoring has occurred here, and if you look to CharacterData of you discover that it returns some classes which are generated on the fly from templates in `/openjdk/make/tools/GenerateCharacter/CharacterData**.java.template` when building java distribution:

// Character <= 0xff (basic latin) is handled by internal fast-path
    // to avoid initializing large tables.
    // Note: performance of this "fast-path" code may be sub-optimal
    // in negative cases for some accessors due to complicated ranges.
    // Should revisit after optimization of table initialization.

static final CharacterData of(int ch) {
    if (ch >>> 8 == 0) {     // fast-path
        return CharacterDataLatin1.instance;
    } else {
        switch(ch >>> 16) {  //plane 00-16
        case(0):
            return CharacterData00.instance;
        case(1):
            return CharacterData01.instance;
        case(2):
            return CharacterData02.instance;
        case(14):
            return CharacterData0E.instance;
        case(15):   // Private Use
        case(16):   // Private Use
            return CharacterDataPrivateUse.instance;
        default:
            return CharacterDataUndefined.instance;
        }
    }
}

I think you could try to run javah in debug mode and see what happens in the two cases, then send a precise bug report to the OpenJDK guys, because the bug has clearly been introduced by this refactoring.

Problem

In java 6, I was able to use JNI in Scala just fine. I would have code like: ``` package mypackage object MyClass { System.loadLibrary("myclass-native") @native def foo(): Int = sys.error("") } ``` And then I'd run: ``` javah -classpath target/scala-2.9.1/classes -d target/jni mypackage.MyClass$ ``` And I'd get my header files just fine. In java 7, I get the following error: ``` Exception in thread "main" java.lang.IllegalArgumentException: Not a valid class name: mypackage.MyClass. at com.sun.tools.javac.api.JavacTool.getTask(JavacTool.java:177) at com.sun.tools.javac.api.JavacTool.getTask(JavacTool.java:68) at com.sun.tools.javah.JavahTask.run(JavahTask.java:509) at com.sun.tools.javah.JavahTask.run(JavahTask.java:335) at com.sun.tools.javah.Main.main(Main.java:46) ``` It's like javah no longer accepts dollar signs in class names, but I need to use the dollar sign in Scala to get the equivalent of a static method. For reference with java 6: ``` $ java -version java version "1.6.0_29" Java(TM) SE Runtime Environment (build 1.6.0_29-b11) Java HotSpot(TM) 64-Bit Server VM (build 20.4-b02, mixed mode) $ javah -version javah version "1.6.0_29" ``` With java 7: ``` $ java -version java version "1.7.0_03" OpenJDK Runtime Environment (IcedTea7 2.1.1pre) (7~u3-2.1.1~pre1-1ubuntu2) OpenJDK 64-Bit Server VM (build 22.0-b10, mixed mode) $ javah -version javah version "1.7.0_03" ``` Has anyone had any luck using javah for JNI with Scala in java 7? Edit Posted as a bug at https://bugs.java.com/bugdatabase/view_bug?bug_id=7185778

Original source