linera-io/linera-protocol · error
Attempt to unflatten an invalid `char`
Error message
Attempt to unflatten an invalid `char`
What it means
A WIT `char` crosses the boundary as an i32 flat value holding the Unicode code point. Unflattening calls `char::from_u32(flat as u32)`, which returns None for values that are not Unicode scalar values — the surrogate range 0xD800-0xDFFF and anything above 0x10FFFF — and `.expect` panics. Receiving such a value means the guest produced a code point that Rust `char`s cannot represent, violating the WIT type contract.
Source
Thrown at linera-witty/src/primitive_types/simple_type.rs:71
unflatten(flat)
}
}
};
}
simple_type!(bool -> i32, 1, { |flat| flat != 0 });
simple_type!(i8 -> i32, 1);
simple_type!(i16 -> i32, 2);
simple_type!(i32 -> i32, 4);
simple_type!(i64 -> i64, 8);
simple_type!(u8 -> i32, 1);
simple_type!(u16 -> i32, 2);
simple_type!(u32 -> i32, 4);
simple_type!(u64 -> i64, 8);
simple_type!(f32 -> f32, 4);
simple_type!(f64 -> f64, 8);
simple_type!(char -> i32, 4,
{ |flat| char::from_u32(flat as u32).expect("Attempt to unflatten an invalid `char`") }
);
View on GitHub (pinned to 6c226ddcb3)
Solutions
- Validate in the guest before passing/returning a char: `char::from_u32(v).ok_or(Error::InvalidCodePoint)`
- Fix surrogate handling: recombine UTF-16 surrogate pairs into one scalar value; never forward a lone surrogate
- If arbitrary u32 values must cross, declare the parameter as u32 in WIT and validate/convert to char on the host side
Example fix
// before (guest): forwards a raw code point unchecked
fn symbol(idx: u32) -> char {
let code = table_lookup(idx); // may be 0xD800..=0xDFFF
unsafe { char::from_u32_unchecked(code) } // host panics on unflatten
}
// after (guest): validate at the boundary
fn symbol(idx: u32) -> Result<char, InvalidChar> {
char::from_u32(table_lookup(idx)).ok_or(InvalidChar)
} Defensive patterns
Strategy: type-guard
Validate before calling
// guest: assert boundary inputs/outputs are valid scalar values
assert!(char::from_u32(code).is_some(), "invalid code point {code:#x}"); Type guard
fn is_valid_scalar(code: u32) -> bool { char::from_u32(code).is_some() } Prevention
- Use char::from_u32 (returns Option) instead of from_u32_unchecked or integer casts
- Never pass UTF-16 surrogates across the WIT boundary; recombine pairs first
- Fuzz char-typed entry points with valid scalar values (0..=0x10FFFF minus surrogates)
When it happens
Trigger: A guest passes 0xD800..=0xDFFF or >0x10FFFF where the WIT interface declares char; the guest builds its 'char' from raw u32 bits (e.g. UTF-16 surrogate pieces) without validation.
Common situations: Guests ported from C/JS that treat chars as plain 32-bit ints; incorrect UTF-16 surrogate-pair handling that forwards lone surrogates; fuzz tests feeding arbitrary u32 into char-typed entry points.
Related errors
- Invalid `i32` stored in `i64`
- A Wasm runtime is required to load user applications. Please
- BytecodeTooLarge
- MaximumFuelExceeded
- ReentrantCall
AI-assisted analysis of linera-io/linera-protocol@6c226ddcb3 (2026-08-22).
Data as JSON: /api/errors/4927ddbeaca0f7b2.
Report an issue: GitHub.