robd003 opened a new issue, #7905:
URL: https://github.com/apache/opendal/issues/7905

   ### Describe the bug
   
   Several `native` method declarations in the Java binding do not match the 
return type of their Rust JNI implementations. One of them is dangerous:
   
   `bindings/java/src/main/java/org/apache/opendal/OperatorOutputStream.java`:
   
   ```java
   private static native byte[] writeBytes(long writer, byte[] bytes);
   ```
   
   but `bindings/java/src/operator_output_stream.rs` implements it returning 
nothing:
   
   ```rust
   pub unsafe extern "system" fn 
Java_org_apache_opendal_OperatorOutputStream_writeBytes(
       mut env: JNIEnv,
       _: JClass,
       writer: *mut blocking::Writer,
       content: JByteArray,
   ) {
       intern_write_bytes(&mut env, &mut *writer, content).unwrap_or_else(|e| {
           e.throw(&mut env);
       })
   }
   ```
   
   Because the Java side declares an **object** return type, the JVM's native 
method wrapper interprets whatever garbage is left in the return register as a 
JNI local reference and resolves it. The result depends on platform and on the 
codegen of the shipped native library:
   
   | artifact (0.48.2, from Maven Central) | result of any 
`OperatorOutputStream.write/flush/close` |
   |---|---|
   | `linux-aarch_64` | deterministic, messageless 
`java.lang.NullPointerException` from `writeBytes(Native Method)` — for 
**every** service (reproduced with `fs` and `s3`) |
   | `linux-x86_64` | happens to work |
   | `osx-aarch_64` | happens to work |
   
   This makes `OperatorOutputStream` completely unusable on aarch64 Linux (AWS 
Graviton & Docker on Apple Silicon):
   
   ```
   java.lang.NullPointerException
           at org.apache.opendal.OperatorOutputStream.writeBytes(Native Method)
           at 
org.apache.opendal.OperatorOutputStream.flush(OperatorOutputStream.java:73)
           at 
org.apache.opendal.OperatorOutputStream.write(OperatorOutputStream.java:61)
   ```
   
   Downstream report: https://github.com/crate/crate/issues/19723 (and 
https://github.com/crate/crate/issues/19586), where this broke S3/Azure/GCS 
snapshot repositories in CrateDB on aarch64 Linux.
   
   The mismatch is present from the introduction of `OperatorOutputStream` 
(#4626) through current `main`.
   
   ### Steps to Reproduce
   
   On linux-aarch64 (e.g. `docker run --platform linux/arm64 
eclipse-temurin:26-noble`) with `org.apache.opendal:opendal:0.48.2` and 
classifier `linux-aarch_64`:
   
   ```java
   import org.apache.opendal.*;
   
   public class Repro {
       public static void main(String[] args) throws Exception {
           var config = new ServiceConfig() {
               public String scheme() { return "fs"; }
               public java.util.Map<String, String> configMap() {
                   return java.util.Map.of("root", 
System.getProperty("java.io.tmpdir"));
               }
           };
           try (AsyncExecutor executor = AsyncExecutor.createTokioExecutor(2);
                Operator op = AsyncOperator.of(config, executor).blocking();
                var out = op.createOutputStream("repro.dat")) {
               out.write("hello".getBytes());
               out.flush();   // <- throws java.lang.NullPointerException at 
writeBytes(Native Method)
           }
       }
   }
   ```
   
   The same program works on linux-x86_64 and osx-aarch_64.
   
   Recompiling `OperatorOutputStream` with `private static native void 
writeBytes(...)` (no other change, same native library) fixes the NPE on 
linux-aarch64 confirming the return-type mismatch is the cause.
   
   ### Expected Behavior
   
   Align the Java declarations with the Rust implementations. Dangerous 
(object-vs-void) mismatch:
   
   - `OperatorOutputStream`: `byte[] writeBytes(long, byte[])` → `void`
   
   Harmless but wrong (`long`-vs-void, garbage primitive is discarded), for 
consistency:
   
   - `OperatorOutputStream`: `long disposeWriter(long)` → `void`
   - `OperatorInputStream`: `long disposeReader(long)` → `void`
   - `Operator`: `long createDir(long, String)` → `void`
   - `Operator`: `long copy(long, String, String)` → `void`
   - `Operator`: `long rename(long, String, String)` → `void`
   
   (Alternatively the Rust functions could return real values, but nothing uses 
the current return values on the Java side.)
   
   ### Additional Context
   
   _No response_
   
   ### Are you willing to submit a PR to fix this bug?
   
   - [ ] Yes, I would like to submit a PR.


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to