Description
Hi!
I found a issue in the implementation of eval/invoke
in Language.Wasm.Interpreter
.
Right now, invocation arguments are only type-matched pairwise using zipWith checkValType
. This silently drops any non-matched argument when the two lists have different length, providing confusing results because the indices of the locals get shifted to the left.
For instance, if I do:
ghci> m = Module {types = [FuncType {params = [I32,I32], results = [I32]}], functions = [Function {funcType = 0, localTypes = [I32], body = [GetLocal 1]}], tables = [], mems = [], globals = [], elems = [], datas = [], start = Nothing, imports = [], exports = [Export {name = "foo", desc = ExportFunc 0}]}
ghci> Right vm = validate m
ghci> Right (mi, s) <- instantiate emptyStore mempty vm
Then, invoking "foo" with a single argument doesn't fail, but instead it returns the value of the first zero-initialized local:
ghci> invokeExport s mi "foo" [VI32 10, VI32 20]
Just [VI32 20]
ghci> invokeExport s mi "foo" [VI32 10]
Just [VI32 0]
Running this in the reference implementation works as expected, raising a runtime error:
$ ./wasm -i foo.wasm -e '(invoke "foo" (i32.const 10) (i32.const 20))'
20 : i32
$ ./wasm -i foo.wasm -e '(invoke "foo" (i32.const 10))'
foo.wasm:0x30-0x37: runtime crash: wrong number or types of arguments
The easiest way of solving this could be to add a small check at the top of eval
:
eval :: Natural -> Store -> FunctionInstance -> [Value] -> IO (Maybe [Value])
eval 0 _ _ _ = return Nothing
eval _ _ FunctionInstance { funcType } args | length args /= length (params funcType) = return Nothing
eval budget store FunctionInstance { funcType, moduleInstance, code = Function { localTypes, body} } args = ...
I fixed the code in my fork just like this, but I would be happy to open a PR with a different solution if you like.
/Agustín