You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Bug: JNI Connection.execute crashes the JVM on non-Value parameters #751
Filed against the bindings repo, where the code lives: LadybugDB/ladybug-java#11. Cross-posting here for visibility, since tools/java_api ships with this repo.
Ladybug version
com.ladybugdb:lbug 0.19.0 (Maven Central). JNI source is tools/java_api @ a0e7728, the tip of ladybug-javamain and the commit this repo pins at both v0.18.1 and v0.19.0.
What operating system are you using?
Debian 13, Linux x86-64, OpenJDK 25.0.3.
What happened?
Passing a parameter that is not a com.ladybugdb.Value to Connection.execute kills the JVM with SIGSEGV in lbug_value_clone, with no exception and no stack unwinding. bindJavaParamsToPreparedStatement hands each map value straight to getValue (src/jni/lbug_java.cpp:516), which reinterpret_casts a field that only exists on Value (src/jni/lbug_java.cpp:381), with no IsInstanceOf check.
Connection.execute is declared Map<String, Value>, so this is only reachable through erasure: a raw or unchecked Map, reflection, or a JVM language that does not enforce Java generics (my case: Clojure). Expected: an IllegalArgumentException, or conversion of boxed values through the ladder Java_com_ladybugdb_Native_lbugValueCreateValue already implements. Fix options and suggested tests are in LadybugDB/ladybug-java#11.
Are there known steps to reproduce?
Map<String, Object> raw = newHashMap<>();
raw.put("p_id", UUID.fromString("11111111-1111-1111-1111-111111111111"));
raw.put("p_name", "hello");
@SuppressWarnings("unchecked")
Map<String, Value> params = (Map<String, Value>) (Map<?, ?>) raw;
try (PreparedStatementps = conn.prepare("MATCH (n:T {id: $p_id}) SET n.name = $p_name;")) {
conn.execute(ps, params); // process dies here
}
# SIGSEGV (0xb) at pc=0x00007fc0f4ba89b3
# C [liblbug_java_native....so+0x29a9b3] lbug_value_clone+0x23
siginfo: si_signo: 11 (SIGSEGV), si_code: 128 (SI_KERNEL), si_addr: 0x0000000000000000
A single unwrapped entry suffices: one bare String parameter crashes identically. Map.of("p_id", new Value(uuid)) works.
Status update, This is being fixed in LadybugDB/ladybug-java#12, on branch fix/reject-non-value-params, which is open and marked Fixes: #11. Recording what it does here since this issue is the cross-post and the fix lands in the other repo.
The PR takes both options from the original report rather than choosing between them:
Java-side coercion.Connection.execute widens from Map<String, Value> to Map<String, ?>, and a new private coerceParams / coerceParam pair converts boxed values via a pattern-matching switch over Value, Boolean, Byte, Short, Integer, Long, BigInteger, Float, Double, BigDecimal, String, InternalID, UUID, LocalDate, Instant, Duration.
JNI guard.bindJavaParamsToPreparedStatement keeps an env->IsInstanceOf(value, J_C_Value) check as a contract backstop and throws instead of reinterpret_casting, and lbugConnectionExecute gains an env->ExceptionCheck() early return after binding so a pending Java exception propagates rather than being trampled.
Unsupported types now raise IllegalArgumentException naming the parameter and the offending class. null raises too, pointing at Value.createNull().
Seven tests in PreparedStatementTest, including a regression test for the previously-working Value-wrapped path and a mixed raw-plus-Value case.
Two things worth flagging for anyone reading this issue later:
The reproducer in the body no longer compiles as written against that branch, which is the point. The unchecked cast to Map<String, Value> exists only because the old signature demanded it. With Map<String, ?> the crashing call becomes the natural conn.execute(ps, Map.of("p_id", uuid, "p_name", "hello")), and it now works rather than killing the JVM. UUID is in the accepted list, so the exact case that started this is covered.
The PR raises the Java baseline from 8/11 to 21 (CMAKE_JAVA_COMPILE_FLAGS, build.gradle source/target compatibility, the Kotlin jvmTarget, and the published POM properties), because coerceParam uses a pattern-matching switch. That is a consumer-visible change to the published artifact and a bigger decision than the crash itself, so it is worth a deliberate call rather than riding along with a bug fix. The same coercion expressed as an if/instanceof chain would hold the old baseline if that matters to you.
No action needed from me on this issue; happy to test the branch against the original Clojure caller if that is useful.
Activity
alvorithm commentedon Jul 30, 2026
Status update,
This is being fixed in LadybugDB/ladybug-java#12, on branch
fix/reject-non-value-params, which is open and markedFixes: #11. Recording what it does here since this issue is the cross-post and the fix lands in the other repo.The PR takes both options from the original report rather than choosing between them:
Connection.executewidens fromMap<String, Value>toMap<String, ?>, and a new privatecoerceParams/coerceParampair converts boxed values via a pattern-matchingswitchoverValue,Boolean,Byte,Short,Integer,Long,BigInteger,Float,Double,BigDecimal,String,InternalID,UUID,LocalDate,Instant,Duration.bindJavaParamsToPreparedStatementkeeps anenv->IsInstanceOf(value, J_C_Value)check as a contract backstop and throws instead ofreinterpret_casting, andlbugConnectionExecutegains anenv->ExceptionCheck()early return after binding so a pending Java exception propagates rather than being trampled.IllegalArgumentExceptionnaming the parameter and the offending class.nullraises too, pointing atValue.createNull().PreparedStatementTest, including a regression test for the previously-workingValue-wrapped path and a mixed raw-plus-Valuecase.Two things worth flagging for anyone reading this issue later:
Map<String, Value>exists only because the old signature demanded it. WithMap<String, ?>the crashing call becomes the naturalconn.execute(ps, Map.of("p_id", uuid, "p_name", "hello")), and it now works rather than killing the JVM.UUIDis in the accepted list, so the exact case that started this is covered.CMAKE_JAVA_COMPILE_FLAGS,build.gradlesource/target compatibility, the KotlinjvmTarget, and the published POM properties), becausecoerceParamuses a pattern-matchingswitch. That is a consumer-visible change to the published artifact and a bigger decision than the crash itself, so it is worth a deliberate call rather than riding along with a bug fix. The same coercion expressed as anif/instanceofchain would hold the old baseline if that matters to you.No action needed from me on this issue; happy to test the branch against the original Clojure caller if that is useful.