wasmer_interface_types_fl/interpreter/instructions/
strings.rs

1use super::to_native;
2use crate::instr_error;
3use crate::IType;
4use crate::IValue;
5use crate::{
6    errors::{InstructionError, InstructionErrorKind, InstructionResult},
7    interpreter::stack::Stackable,
8    interpreter::Instruction,
9    interpreter::Runtime,
10};
11
12use it_lilo::traits::DEFAULT_MEMORY_INDEX;
13
14use futures::future::BoxFuture;
15use futures::FutureExt;
16
17struct StringLiftMemory {
18    instruction: Instruction,
19}
20
21impl_async_executable_instruction!(
22    string_lift_memory(instruction: Instruction) -> _ {
23        Box::new(StringLiftMemory{instruction})
24    }
25    StringLiftMemory {
26        fn execute<'args>(&'args self, runtime: &'args mut Runtime<Instance, Export, LocalImport, Memory, MemoryView, Store>) -> BoxFuture<InstructionResult<()>> {
27            async move {
28                let instruction = &self.instruction;
29                let mut inputs = runtime.stack.pop(2).ok_or_else(|| {
30                    InstructionError::from_error_kind(
31                        instruction.clone(),
32                        InstructionErrorKind::StackIsTooSmall { needed: 2 },
33                    )
34                })?;
35
36                let memory_index = DEFAULT_MEMORY_INDEX;
37                let memory = runtime
38                    .wasm_instance
39                    .memory(memory_index)
40                    .ok_or_else(|| {
41                        InstructionError::from_error_kind(
42                            instruction.clone(),
43                            InstructionErrorKind::MemoryIsMissing { memory_index },
44                        )
45                    })?;
46
47                let pointer = to_native::<i32>(inputs.remove(0), instruction.clone())? as u32;
48                let length = to_native::<i32>(inputs.remove(0), instruction.clone())? as u32;
49                let memory_view = memory.view();
50
51                if length == 0 {
52                    runtime.stack.push(IValue::String("".into()));
53
54                    return Ok(())
55                }
56
57                memory_view
58                    .check_bounds(runtime.store, pointer, length)
59                    .map_err(|e| InstructionError::from_memory_access(instruction.clone(), e))?;
60
61                let data = memory_view.read_vec(runtime.store, pointer, length);
62                let string = String::from_utf8(data)
63                    .map_err(|error| InstructionError::from_error_kind(instruction.clone(), InstructionErrorKind::String(error)))?;
64
65                log::debug!("string.lift_memory: pushing {:?} on the stack", string);
66                runtime.stack.push(IValue::String(string));
67
68                Ok(())
69            }.boxed()
70        }
71    }
72);
73
74struct StringLowerMemoryAsync {
75    instruction: Instruction,
76}
77
78impl_async_executable_instruction!(
79    string_lower_memory(instruction: Instruction) -> _ {
80        Box::new(StringLowerMemoryAsync {instruction})
81    }
82    StringLowerMemoryAsync {
83        fn execute<'args>(&'args self, runtime: &'args mut Runtime<Instance, Export, LocalImport, Memory, MemoryView, Store>) -> BoxFuture<InstructionResult<()>> {
84            async move {
85                let instruction = &self.instruction;
86                let mut inputs = runtime.stack.pop(2).ok_or_else(|| {
87                    InstructionError::from_error_kind(
88                        instruction.clone(),
89                        InstructionErrorKind::StackIsTooSmall { needed: 2 },
90                    )
91                })?;
92
93                let string_pointer = to_native::<i32>(inputs.remove(0), instruction.clone())? as u32;
94                let string: String = to_native(inputs.remove(0), instruction.clone())?;
95                let string_bytes = string.as_bytes();
96                let string_length: u32 = string_bytes.len() as u32;
97
98                let instance = &mut runtime.wasm_instance;
99                let memory_index = DEFAULT_MEMORY_INDEX;
100                let memory_view = instance
101                    .memory_view(memory_index)
102                    .ok_or_else(|| {
103                        InstructionError::from_error_kind(
104                            instruction.clone(),
105                            InstructionErrorKind::MemoryIsMissing { memory_index },
106                        )
107                    })?;
108
109                memory_view
110                    .check_bounds(runtime.store, string_pointer, string_length)
111                    .map_err(|e| InstructionError::from_memory_access(instruction.clone(), e))?;
112
113                memory_view.write_bytes(runtime.store, string_pointer, string_bytes);
114
115                log::debug!("string.lower_memory: pushing {}, {} on the stack", string_pointer, string_length);
116                runtime.stack.push(IValue::I32(string_pointer as i32));
117                runtime.stack.push(IValue::I32(string_length as i32));
118
119                Ok(())
120            }.boxed()
121        }
122    }
123);
124
125struct StringSize {
126    instruction: Instruction,
127}
128
129impl_async_executable_instruction!(
130    string_size(instruction: Instruction) -> _ {
131        Box::new( StringSize{instruction } )
132    }
133
134    StringSize {
135        fn execute<'args>(&'args self, runtime: &'args mut Runtime<Instance, Export, LocalImport, Memory, MemoryView, Store>) -> BoxFuture<InstructionResult<()>> {
136            async move {
137            let instruction = &self.instruction;
138            match runtime.stack.pop1() {
139                Some(IValue::String(string)) => {
140                    let length = string.len() as i32;
141
142                    log::debug!("string.size: pushing {} on the stack", length);
143                    runtime.stack.push(IValue::I32(length));
144
145                    Ok(())
146                },
147
148                Some(value) => instr_error!(
149                    instruction.clone(),
150                    InstructionErrorKind::InvalidValueOnTheStack {
151                        expected_type: IType::String,
152                        received_value: (&value).clone(),
153                    }
154                ),
155
156                None => instr_error!(
157                    instruction.clone(),
158                    InstructionErrorKind::StackIsTooSmall { needed: 1 }
159                ),
160                }
161            }.boxed()
162        }
163    }
164);
165
166#[cfg(test)]
167mod tests {
168    test_executable_instruction!(
169        test_string_lift_memory =
170            instructions: [
171                Instruction::ArgumentGet { index: 0 },
172                Instruction::ArgumentGet { index: 1 },
173                Instruction::StringLiftMemory,
174            ],
175            invocation_inputs: [
176                IValue::I32(0),
177                //              ^^^^^^ pointer
178                IValue::I32(13),
179                //              ^^^^^^^ length
180            ],
181            instance: Instance {
182                memory: Memory::new("Hello, World!".as_bytes().iter().map(|u| Cell::new(*u)).collect()),
183                ..Default::default()
184            },
185            stack: [IValue::String("Hello, World!".into())],
186    );
187
188    test_executable_instruction!(
189        test_string_lift_memory__empty_string =
190            instructions: [
191                Instruction::ArgumentGet { index: 0 },
192                Instruction::ArgumentGet { index: 1 },
193                Instruction::StringLiftMemory,
194            ],
195            invocation_inputs: [
196                IValue::I32(0),
197                IValue::I32(0),
198            ],
199            instance: Instance {
200                memory: Memory::new(vec![]),
201                ..Default::default()
202            },
203            stack: [IValue::String("".into())],
204    );
205
206    test_executable_instruction!(
207        test_string_lift_memory__negative_pointer =
208            instructions: [
209                Instruction::ArgumentGet { index: 0 },
210                Instruction::ArgumentGet { index: 1 },
211                Instruction::StringLiftMemory,
212            ],
213            invocation_inputs: [
214                IValue::I32(-42),
215                IValue::I32(13),
216            ],
217            instance: Instance {
218                memory: Memory::new("Hello!".as_bytes().iter().map(|u| Cell::new(*u)).collect()),
219                ..Default::default()
220            },
221            error: r#"`string.lift_memory` attempted to convert `pointer` but it appears to be a negative value"#,
222    );
223
224    test_executable_instruction!(
225        test_string_lift_memory__negative_length =
226            instructions: [
227                Instruction::ArgumentGet { index: 0 },
228                Instruction::ArgumentGet { index: 1 },
229                Instruction::StringLiftMemory,
230            ],
231            invocation_inputs: [
232                IValue::I32(0),
233                IValue::I32(-1),
234            ],
235            instance: Instance {
236                memory: Memory::new("Hello!".as_bytes().iter().map(|u| Cell::new(*u)).collect()),
237                ..Default::default()
238            },
239            error: r#"`string.lift_memory` attempted to convert `length` but it appears to be a negative value"#,
240    );
241
242    test_executable_instruction!(
243        test_string_lift_memory__read_out_of_memory =
244            instructions: [
245                Instruction::ArgumentGet { index: 0 },
246                Instruction::ArgumentGet { index: 1 },
247                Instruction::StringLiftMemory,
248            ],
249            invocation_inputs: [
250                IValue::I32(0),
251                //              ^^^^^^ pointer
252                IValue::I32(13),
253                //              ^^^^^^^ length is too long
254            ],
255            instance: Instance {
256                memory: Memory::new("Hello!".as_bytes().iter().map(|u| Cell::new(*u)).collect()),
257                ..Default::default()
258            },
259            error: r#"`string.lift_memory` read out of the memory bounds (index 13 > memory length 6)"#,
260    );
261
262    test_executable_instruction!(
263        test_string_lift_memory__invalid_encoding =
264            instructions: [
265                Instruction::ArgumentGet { index: 0 },
266                Instruction::ArgumentGet { index: 1 },
267                Instruction::StringLiftMemory,
268            ],
269            invocation_inputs: [
270                IValue::I32(0),
271                //              ^^^^^^ pointer
272                IValue::I32(4),
273                //              ^^^^^^ length is too long
274            ],
275            instance: Instance {
276                memory: Memory::new(vec![0, 159, 146, 150].iter().map(|b| Cell::new(*b)).collect::<Vec<Cell<u8>>>()),
277                ..Default::default()
278            },
279            error: r#"`string.lift_memory` invalid utf-8 sequence of 1 bytes from index 1"#,
280    );
281
282    test_executable_instruction!(
283        test_string_lift_memory__stack_is_too_small =
284            instructions: [
285                Instruction::ArgumentGet { index: 0 },
286                Instruction::StringLiftMemory,
287                //           ^^^^^^^^^^^^^^^^ `string.lift_memory` expects 2 values on the stack, only one is present.
288            ],
289            invocation_inputs: [
290                IValue::I32(0),
291                IValue::I32(13),
292            ],
293            instance: Instance::new(),
294            error: r#"`string.lift_memory` needed to read `2` value(s) from the stack, but it doesn't contain enough data"#,
295    );
296
297    test_executable_instruction!(
298        test_string_lower_memory =
299            instructions: [
300                Instruction::ArgumentGet { index: 0 },
301                Instruction::StringSize,
302                Instruction::CallCore { function_index: 43 },
303                Instruction::ArgumentGet { index: 0 },
304                Instruction::StringLowerMemory,
305
306            ],
307            invocation_inputs: [IValue::String("Hello, World!".into())],
308            instance: Instance::new(),
309            stack: [
310                IValue::I32(0),
311                //              ^^^^^^ pointer
312                IValue::I32(13),
313                //              ^^^^^^^ length
314            ]
315    );
316
317    test_executable_instruction!(
318        test_string__roundtrip =
319            instructions: [
320                Instruction::ArgumentGet { index: 0 },
321                Instruction::StringSize,
322                Instruction::CallCore { function_index: 43 },
323                Instruction::ArgumentGet { index: 0 },
324                Instruction::StringLowerMemory,
325                Instruction::StringLiftMemory,
326            ],
327            invocation_inputs: [IValue::String("Hello, World!".into())],
328            instance: Instance::new(),
329            stack: [IValue::String("Hello, World!".into())],
330    );
331
332    test_executable_instruction!(
333        test_string_lower_memory__stack_is_too_small =
334            instructions: [
335                Instruction::StringLowerMemory,
336            ],
337            invocation_inputs: [],
338            instance: Instance::new(),
339            error: r#"`string.lower_memory` needed to read `2` value(s) from the stack, but it doesn't contain enough data"#,
340    );
341
342    test_executable_instruction!(
343        test_string_size =
344            instructions: [
345                Instruction::ArgumentGet { index: 0 },
346                Instruction::StringSize,
347            ],
348            invocation_inputs: [IValue::String("Hello, World!".into())],
349            instance: Instance::new(),
350            stack: [IValue::I32(13)],
351    );
352
353    test_executable_instruction!(
354        test_string_size__stack_is_too_small =
355            instructions: [
356                Instruction::StringSize,
357            ],
358            invocation_inputs: [],
359            instance: Instance::new(),
360            error: r#"`string.size` needed to read `1` value(s) from the stack, but it doesn't contain enough data"#,
361    );
362
363    test_executable_instruction!(
364        test_string_size__invalid_value_on_the_stack =
365            instructions: [
366                Instruction::ArgumentGet { index: 0 },
367                Instruction::StringSize,
368            ],
369            invocation_inputs: [IValue::I32(42)],
370            instance: Instance::new(),
371            error: r#"`string.size` read a value of type `I32` from the stack, but the type `String` was expected"#,
372    );
373}