highlight.js

Monday, August 16, 2021

Javet Supports Mac OS Now

Javet is Java + V8 (JAVa + V + EighT). It is an awesome way of embedding Node.js and V8 in Java.

I'm very happy to announce Javet supports Mac OS x86_64 from v0.9.9.

Maven

<dependency>
    <groupId>com.caoccao.javet</groupId>
    <artifactId>javet-macos</artifactId>
    <version>0.9.9</version>
</dependency>

Gradle Kotlin DSL

implementation("com.caoccao.javet:javet-macos:0.9.9")

Gradle Groovy DSL

implementation 'com.caoccao.javet:javet-macos:0.9.9'

Hello Javet

// Node.js Mode
try (V8Runtime v8Runtime = V8Host.getNodeInstance().createV8Runtime()) {
    System.out.println(v8Runtime.getExecutor("'Hello Javet'").executeString());
}

// V8 Mode
try (V8Runtime v8Runtime = V8Host.getV8Instance().createV8Runtime()) {
    System.out.println(v8Runtime.getExecutor("'Hello Javet'").executeString());
}

Wednesday, August 11, 2021

JNI Symbol Conflicts in Mac OS

Background

When I was adding Mac OS support to my open source project Javet, I found a weird behavior caused by JNI symbol conflicts which resulted in JVM 8 core-dump.

Javet is Java + V8 (JAVa + V + EighT). It is an awesome way of embedding Node.js and V8 in Java. It's embedding behavior relies on 2 JNI libraries one for Node.js and one for V8. Both of them are loaded by dedicated classloader and share the same API and symbols, except that the Node.js one exposes more Node.js flavored symbols.

In Javet unit test, it loads both Node.js and V8 libraries and performs complicated test. The test goes well in Linux and Windows, but core-dumps in Mac OS.

Analysis

The dump file shows the call stack crosses the library boundary.
...node...dylib
...node...dylib
...node...dylib <== Something wrong happens here.
...v8...dylib
...v8...dylib
...v8...dylib
It seems JVM in Mac OS registers global symbols in the same memory space, so that the V8 library calls into Node.js library. Obviously, that for sure leads to core-dump.

Failed Attempts

  • I tried various distributions of JDK 8. None of them worked. It seems to be the Mac OS' behavior.
  • I tried to adjust the CMakeLists.txt. All my attempts failed.

Fix

I kept reviewing the call stack and realized the issue might be gone if these symbols were not global symbols.

So I created jni/exported_symbols_list.txt with the following content.
_JNI_OnLoad
_JNI_OnUnload
_Java_com_caoccao_javet_*
_napi_*
Then, I updated CMakeLists.txt with the following content.
target_link_libraries(Javet PUBLIC -exported_symbols_list ${CMAKE_SOURCE_DIR}/jni/exported_symbols_list.txt)
And, it works, no more core-dump with all test cases pass.

Monday, August 2, 2021

Javet - Java and JavaScript Interop

Javet is Java + V8 (JAVa + V + EighT). It is an awesome way of embedding Node.js and V8 in Java.

From v0.9.8, Javet allows injecting arbitrary Java objects into V8 which enables the complete interop between Java and JavaScript. To enable this feature, application just needs to call v8Runtime.setConverter(new JavetProxyConverter());. Here are 3 examples.

Inject a Static Class

v8Runtime.getGlobalObject().set("System", System.class);
v8Runtime.getExecutor("function main() {\n" +
        // Java reference can be directly called in JavaScript.
        "  System.out.println('Hello from Java');\n" +
        // Java reference can be directly assigned to JavaScript variable.
        "  const println = System.out.println;\n" +
        // Java reference can be directly assigned to JavaScript variable.
        "  println('Hello from JavaScript');\n" +
        "}\n" +
        "main();").executeVoid();
v8Runtime.getGlobalObject().delete("System");

/*
 * Output:
 *   Hello from Java
 *   Hello from JavaScript
 */

Inject an Enum

v8Runtime.getGlobalObject().set("Color", Color.class);
System.out.println(v8Runtime.getExecutor("Color.pink.toString();").executeString());
System.out.println("The enum in JavaScript is the one in Java: " +
        (Color.pink == (Color) v8Runtime.getExecutor("Color.pink;").executeObject()));
v8Runtime.getGlobalObject().delete("Color");

/*
 * Output:
 *   java.awt.Color[r=255,g=175,b=175]
 *   The enum in JavaScript is the one in Java: true
 */

Inject a Pattern

Pattern pattern = Pattern.compile("^\\d+$");
v8Runtime.getExecutor("function main(pattern) {\n" +
        "  return [\n" +
        "    pattern.matcher('123').matches(),\n" +
        "    pattern.matcher('abc').matches(),\n" +
        "  ];\n" +
        "}").executeVoid();
System.out.println(v8Runtime.getGlobalObject().invokeObject("main", pattern).toString());

/*
 * Output:
 *   [true, false]
 */

Inject a StringBuilder

v8Runtime.getGlobalObject().set("StringBuilder", StringBuilder.class);
 System.out.println(v8Runtime.getExecutor("function main() {\n" +
         "  return new StringBuilder('Hello').append(' from StringBuilder').toString();\n" +
         "}\n" +
         "main();").executeString());
 v8Runtime.getGlobalObject().delete("StringBuilder");

/*
 * Output:
 *   Hello from StringBuilder
 */

IDEA / HandBrake / WSL Port Conflict

Recently I upgraded HandBrake to the latest version, however, it stopped working. The root cause is TCP port conflict. As usual, I ran net s...