kawamuray / wasmtime-java

Java or JVM-language binding for Wasmtime
Apache License 2.0
127 stars 29 forks source link

Implement accessing Module Imports #31

Closed BjoernAkAManf closed 2 years ago

BjoernAkAManf commented 2 years ago

This is required to load arbitrary WASM, where the order of imports may not be known beforehand. Also helpful to understand potential blast radius of a module in case it has been compromised.

I feel like the current design is not great, given that you cannot try catch with an array and need to manually dispose of the ImportType. Also the need to export a custom struct does not feel good. However as a novice in regards to rust, i felt like this was the most accessible way.

BjoernAkAManf commented 2 years ago

Note: Proper introspection also requires the type information. That is currently lost through the conversion to string.

I'm currently trying to implement an alternative that would implement accessing that properly.

BjoernAkAManf commented 2 years ago

I need to add additional tests for the other types that could be imported, otherwise i think this works great.

BjoernAkAManf commented 2 years ago

One thing i noticed, that some Errors are not handled by the generated Wrapper in a way, that does not just throw a meaningless "Method not found". Is there any way i can handle that gracefully without manually handeling errors?

For example:

fn imports(env: &JNIEnv, this: JObject) -> std::result::Result<jobjectArray, Self::Error> {
        println!("X");
        let r = imports_impl(env, this);
        return match r {
            Ok(a) => {
                println!("YYY: {a:?}");
                Ok(a)
            }
            Err(b) => {
                let x = env.throw(b);
                println!("YYYYY #{x:?}");
                Err(Error::NotImplemented)
            }
        };
    }

Will cause a dump, due to an uncaught exception. Passing in more information would make debugging Tests easier without necessarily configuring a Rust Debugger / Print Debugging.

Otherwise the Global Implementation is almost done.

kawamuray commented 2 years ago

Is there any way i can handle that gracefully without manually handeling errors?

Hmm, I don't get what's the problem here. Can you explain much detail, or apparently it's no longer a problem?

Looking the latest implementation, GlobalType looks good indeed. So for the remaining Memory memory() and Table table() methods of ImportType, you can choice either to implement MemoryType and TableType to fix the method signature, or delete them for now.

BjoernAkAManf commented 2 years ago

The problem seems to be, that some Java Errors are not handled properly. One such issue was specifying the Java Constructor Signature correctly once. I was able to catch that with the code above, but not the code that was generated.

This is a somewhat minor issue, but can be quite annoying. Might also be me being bad at debugging Rust <-> Java Code though.

The only thing i will leave unimplemented will be Instance and Module, the module linking proposal will be more problematic to implement as is. In particular, as far as i understand it, we would have to create Disposable Objects for Instance or Modules and manage them both in Java and Rust. However i might be wrong there, yet to read about that proposal in more detail.

kawamuray commented 2 years ago

So the error caused by mismatching method (constructor) signature is a "no such method" exception and it is handled here, then set to be thrown again as-is: https://github.com/kawamuray/wasmtime-java/blob/master/wasmtime-jni/src/errors.rs#L45

So it is handled in the generated code, but I guess your point is that at java code level it is difficult to know where of the Rust code caused that exception? Then it makes sense. One possible improvement is to embed backtrace information when such exception is generated, but that's not easy with the current impl as in jni::errors::Error automatically gets converted into a Error::Jni by ? operator. Hopefully, such error occurs only when we make a mistake while implementing Rust glue code and can catch it at testing so that we can embed arbitrary debug code to figure out why.