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 IValue::I32(13),
179 ],
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 IValue::I32(13),
253 ],
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 IValue::I32(4),
273 ],
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 ],
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 IValue::I32(13),
313 ]
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}