{"record":{"id":"ff258005cc36b8e3","repo":"wasmerio/wasmer","slug":"cannot-read-non-scalar-value-from-memory","errorCode":null,"errorMessage":"Cannot read non-scalar value from memory","messagePattern":"Cannot read non-scalar value from memory","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"error","filePath":"lib/wasix/src/syscalls/wasix/call_dynamic.rs","lineNumber":68,"sourceCode":"        }\n    }};\n}\n\nfn read_value(\n    memory: &MemoryView,\n    offset: &mut u64,\n    max: u64,\n    strict: bool,\n    ty: &Type,\n) -> Result<Option<Value>, MemoryAccessError> {\n    match ty {\n        Type::I32 => read_value!(memory, *offset, max, strict, i32, I32, 4),\n        Type::I64 => read_value!(memory, *offset, max, strict, i64, I64, 8),\n        Type::F32 => read_value!(memory, *offset, max, strict, f32, F32, 4),\n        Type::F64 => read_value!(memory, *offset, max, strict, f64, F64, 8),\n        Type::V128 => read_value!(memory, *offset, max, strict, u128, V128, 16),\n        // ExternRef, FuncRef, and ExceptionRef cannot be represented as byte slices\n        _ => panic!(\"Cannot read non-scalar value from memory\"),\n    }\n}\n\n/// Call a function from the `__indirect_function_table` with parameters and results from memory.\n///\n/// This function can be used to call functions whose types are not known at\n/// compile time of the caller. It is the callers responsibility to ensure\n/// that the passed parameters and results match the signature of the function\n/// being called.\n///\n/// ### Format of the values and results buffer\n///\n/// The buffers contain all values sequentially. i32, and f32 are 4 bytes,\n/// i64 and f64 are 8 bytes, v128 is 16 bytes.\n///     \n/// For example if the function takes an i32 and an i64, the values buffer will\n/// be 12 bytes long, with the first 4 bytes being the i32 and the next 8\n/// bytes being the i64.","sourceCodeStart":50,"sourceCodeEnd":86,"githubUrl":"https://github.com/wasmerio/wasmer/blob/8c4b9ee9d33fb2068863fbb3d328683e7e6ff7f5/lib/wasix/src/syscalls/wasix/call_dynamic.rs#L50-L86","documentation":"`read_value` in `call_dynamic` reconstructs `Value`s from linear memory by reading fixed-size byte slices. Reference types (ExternRef, FuncRef, ExceptionRef) have no byte-slice representation, so when the function's result/parameter type is a reference the code panics rather than producing an invalid value.","triggerScenarios":"Calling a function via `call_dynamic` whose signature declares a reference-typed result or parameter; reading results back from memory after an indirect call when the callee returns externref/funcref/exceptionref.","commonSituations":"Modules compiled with reference-types enabled being invoked through the WASIX dynamic closure mechanism; type confusion where the indirect-function-table slot type changed but the caller's scalar-only expectations did not; generated stubs that don't filter ref types.","solutions":["Only dynamically call functions whose results and parameters are scalar types; wrap ref-returning functions with a scalar adapter","Return references via a host-managed side table (i32 handle) and read the handle from memory instead","Pre-validate the callee's `Type` list before invoking `call_dynamic` and reject reference types with a proper error","Use the regular (non-dynamic) call path for functions that traffic in reference types"],"exampleFix":"// before\ntype_results.iter().for_each(|t| read_value(memory, &mut off, max, strict, t)); // panics on Ref\n// after\nif type_results.iter().any(|t| matches!(t, Type::Ref(_))) {\n    return Err(Errno::Notsup);\n}","handlingStrategy":"validation","validationCode":"// Ensure all result/param types are scalar before reading/writing memory\nif type_results.iter().any(|t| matches!(t, Type::Ref(_)))\n    || type_params.iter().any(|t| matches!(t, Type::Ref(_)))\n{\n    return Err(Errno::Notsup);\n}","typeGuard":"fn all_scalar_types(types: &[Type]) -> bool {\n    types.iter().all(|t| matches!(t, Type::I32 | Type::I64 | Type::F32 | Type::F64 | Type::V128))\n}","tryCatchPattern":"let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| read_value(memory, &mut off, max, strict, &ty)));\nif result.is_err() { /* ref result: resolve via side table or return Notsup */ }","preventionTips":["Filter ref-typed functions out of the indirect function table used by call_dynamic","Return references via host-managed handles, not raw memory","Assert all_scalar_types on callee signatures in debug builds","Compile guests with reference-types disabled when using dynamic calls"],"tags":["wasix","syscalls","reference-types","memory","unsupported-feature"],"backgroundTag":"non-scalar-value-unsupported","analyzedSha":"8c4b9ee9d33fb2068863fbb3d328683e7e6ff7f5","analyzedAt":"2026-09-01T23:06:31.009Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-09T06:17:21.866Z"}