/src/wasm-tools/crates/wit-smith/src/generate.rs
Line | Count | Source |
1 | | use crate::config::Config; |
2 | | use arbitrary::{Arbitrary, Result, Unstructured}; |
3 | | use indexmap::{IndexMap, IndexSet}; |
4 | | use semver::Version; |
5 | | use std::collections::HashSet; |
6 | | use std::collections::hash_map::{Entry, HashMap}; |
7 | | use std::collections::hash_set::Intersection; |
8 | | use std::fmt::Write; |
9 | | use std::hash::RandomState; |
10 | | use std::mem; |
11 | | use std::rc::Rc; |
12 | | use std::str; |
13 | | use wit_parser::*; |
14 | | |
15 | | pub struct Generator { |
16 | | config: Config, |
17 | | packages: Packages, |
18 | | next_interface_id: u32, |
19 | | } |
20 | | |
21 | | #[derive(PartialEq, Eq, Hash)] |
22 | | pub struct PackageWorldKey { |
23 | | package_name: String, |
24 | | world_name: String, |
25 | | } |
26 | | |
27 | | struct InterfaceGenerator<'a> { |
28 | | generator: &'a mut Generator, |
29 | | file: &'a mut File, |
30 | | unique_names: HashSet<String>, |
31 | | types_in_interface: Vec<Type>, |
32 | | package_name: &'a str, |
33 | | version: Option<Version>, |
34 | | } |
35 | | |
36 | | #[derive(Clone)] |
37 | | struct Type { |
38 | | name: String, |
39 | | size: usize, |
40 | | is_resource: bool, |
41 | | } |
42 | | |
43 | | #[derive(Default)] |
44 | | struct Packages { |
45 | | list: Vec<Package>, |
46 | | packages_with_interfaces: Vec<usize>, |
47 | | packages_with_worlds: Vec<usize>, |
48 | | package_unique_names: IndexMap<PackageWorldKey, HashSet<String>>, |
49 | | } |
50 | | |
51 | | impl Packages { |
52 | 35.2k | fn add_name(&mut self, package_name: String, world_name: String, name: String) { |
53 | 35.2k | let key = PackageWorldKey { |
54 | 35.2k | package_name, |
55 | 35.2k | world_name, |
56 | 35.2k | }; |
57 | 35.2k | let world_names = self |
58 | 35.2k | .package_unique_names |
59 | 35.2k | .entry(key) |
60 | 35.2k | .or_insert_with(HashSet::new); |
61 | 35.2k | world_names.insert(name); |
62 | 35.2k | } |
63 | | |
64 | 0 | fn contains_name(&self, package_name: String, world_name: String, name: &str) -> bool { |
65 | 0 | let key = PackageWorldKey { |
66 | 0 | package_name, |
67 | 0 | world_name, |
68 | 0 | }; |
69 | 0 | if let Some(world_names) = self.package_unique_names.get(&key) { |
70 | 0 | return world_names.contains(name); |
71 | 0 | } |
72 | 0 | false |
73 | 0 | } |
74 | | |
75 | 0 | fn intersect( |
76 | 0 | &self, |
77 | 0 | current_world: PackageWorldKey, |
78 | 0 | include_world: PackageWorldKey, |
79 | 0 | ) -> Option<Intersection<'_, String, RandomState>> { |
80 | 0 | let current_world_names = self.package_unique_names.get(¤t_world); |
81 | 0 | let include_world_names = self.package_unique_names.get(&include_world); |
82 | | |
83 | 0 | if let (Some(current_world_names), Some(include_world_names)) = |
84 | 0 | (current_world_names, include_world_names) |
85 | | { |
86 | 0 | let intersection = current_world_names.intersection(include_world_names); |
87 | 0 | if intersection.clone().count() > 0 { |
88 | 0 | return Some(intersection); |
89 | 0 | } |
90 | 0 | } |
91 | | |
92 | 0 | return None; |
93 | 0 | } |
94 | | } |
95 | | |
96 | | pub struct Package { |
97 | | pub name: PackageName, |
98 | | pub sources: SourceMap, |
99 | | file: File, |
100 | | } |
101 | | |
102 | | #[derive(Clone, Debug)] |
103 | | pub struct PackageName { |
104 | | pub namespace: String, |
105 | | pub name: String, |
106 | | pub version: Option<Version>, |
107 | | } |
108 | | |
109 | | impl Generator { |
110 | 4.46k | pub fn new(config: Config) -> Generator { |
111 | 4.46k | Generator { |
112 | 4.46k | config, |
113 | 4.46k | packages: Default::default(), |
114 | 4.46k | next_interface_id: 0, |
115 | 4.46k | } |
116 | 4.46k | } |
117 | | |
118 | 4.46k | pub fn generate(&mut self, u: &mut Unstructured<'_>) -> Result<Vec<Package>> { |
119 | 4.46k | let mut names = HashSet::new(); |
120 | 12.2k | while self.packages.list.len() < self.config.max_packages |
121 | 11.6k | && (self.packages.list.is_empty() || u.arbitrary()?) |
122 | | { |
123 | 7.80k | let pkg = self.gen_package(u, &mut names)?; |
124 | 7.80k | let i = self.packages.list.len(); |
125 | 7.80k | if pkg.file.interfaces.len() > 0 { |
126 | 3.91k | self.packages.packages_with_interfaces.push(i); |
127 | 3.91k | } |
128 | 7.80k | if pkg.file.worlds.len() > 0 { |
129 | 5.38k | self.packages.packages_with_worlds.push(i); |
130 | 5.38k | } |
131 | 7.80k | self.packages.list.push(pkg); |
132 | | } |
133 | 4.46k | Ok(mem::take(&mut self.packages.list)) |
134 | 4.46k | } |
135 | | |
136 | 7.80k | fn gen_package( |
137 | 7.80k | &mut self, |
138 | 7.80k | u: &mut Unstructured<'_>, |
139 | 7.80k | names: &mut HashSet<String>, |
140 | 7.80k | ) -> Result<Package> { |
141 | 7.80k | let namespace = gen_unique_name(u, names)?; |
142 | 7.80k | let package_name = gen_unique_name(u, names)?; |
143 | | |
144 | 7.80k | let version = if u.arbitrary()? { |
145 | 5.95k | Some(gen_version(u)?) |
146 | | } else { |
147 | 1.84k | None |
148 | | }; |
149 | 7.80k | let mut ret = Package { |
150 | 7.80k | name: PackageName { |
151 | 7.80k | namespace, |
152 | 7.80k | name: package_name.clone(), |
153 | 7.80k | version: version.clone(), |
154 | 7.80k | }, |
155 | 7.80k | file: File::default(), |
156 | 7.80k | sources: SourceMap::new(), |
157 | 7.80k | }; |
158 | | |
159 | | #[derive(Arbitrary, Clone)] |
160 | | enum Generate { |
161 | | Interface, |
162 | | Use, |
163 | | World, |
164 | | Done, |
165 | | } |
166 | | |
167 | 7.80k | let mut items = 0; |
168 | 7.80k | let mut empty = true; |
169 | 7.80k | let mut files = vec![File::default()]; |
170 | 7.80k | let mut package_names = HashSet::new(); |
171 | 7.80k | log::debug!("===================== new package ===================="); |
172 | 54.0k | while items < self.config.max_pkg_items { |
173 | 47.6k | items += 1; |
174 | 47.6k | let max = if files.len() < self.config.max_files_per_package { |
175 | 33.5k | files.len() + 1 |
176 | | } else { |
177 | 14.1k | files.len() |
178 | | }; |
179 | 47.6k | let i = u.int_in_range(0..=max)?; |
180 | 47.6k | let file = match files.get_mut(i) { |
181 | 37.1k | Some(file) => file, |
182 | | None => { |
183 | 10.5k | files.push(ret.file.clone()); |
184 | 10.5k | files.last_mut().unwrap() |
185 | | } |
186 | | }; |
187 | | |
188 | | // Only generate Use/Done if we've already generated a world or interface. This ensures |
189 | | // that we never generate empty packages, which aren't representable. |
190 | 47.6k | let generate = if empty { |
191 | 12.2k | u.choose(&[Generate::World, Generate::Interface])?.clone() |
192 | | } else { |
193 | 35.4k | u.arbitrary()? |
194 | | }; |
195 | | |
196 | 47.6k | match generate { |
197 | | Generate::World => { |
198 | 12.0k | let world_name = |
199 | 12.0k | file.gen_unique_package_name(u, &mut package_names, DefinitionKind::World)?; |
200 | 12.0k | log::debug!("new world `{world_name}` in {i}"); |
201 | 12.0k | let world = |
202 | 12.0k | self.gen_world(u, &world_name, file, &package_name, version.clone())?; |
203 | 12.0k | file.items.push(world); |
204 | | |
205 | | // Insert the world at the package and file level, asserting |
206 | | // uniqueness. |
207 | 12.0k | assert!(ret.file.worlds.insert(world_name.clone())); |
208 | 12.0k | assert!(file.worlds.insert(world_name.clone())); |
209 | 12.0k | let prev = ret.file.namespace.insert( |
210 | 12.0k | world_name.clone(), |
211 | 12.0k | (DefinitionLevel::Package, DefinitionKind::World), |
212 | | ); |
213 | 12.0k | assert!(prev.is_none()); |
214 | | |
215 | | // Insert the definition into all other files as well. |
216 | 24.1k | for file in files.iter_mut() { |
217 | 24.1k | if file.insert_definition(&world_name, DefinitionKind::World) { |
218 | 12.1k | assert!(file.worlds.insert(world_name.clone())); |
219 | 12.0k | } |
220 | | } |
221 | | |
222 | 12.0k | empty = false; |
223 | | } |
224 | | Generate::Interface => { |
225 | 29.5k | let name = file.gen_unique_package_name( |
226 | 29.5k | u, |
227 | 29.5k | &mut package_names, |
228 | 29.5k | DefinitionKind::Interface, |
229 | 0 | )?; |
230 | 29.5k | log::debug!("new interface `{name}` in {i}"); |
231 | 29.5k | let id = self.next_interface_id; |
232 | 29.5k | self.next_interface_id += 1; |
233 | 29.5k | let (src, types) = |
234 | 29.5k | self.gen_interface(u, Some(&name), file, &package_name, None, None)?; |
235 | 29.5k | file.items.push(src); |
236 | 29.5k | if types.is_empty() { |
237 | 21.9k | continue; |
238 | 7.56k | } |
239 | 7.56k | let interface = FileInterface { |
240 | 7.56k | name, |
241 | 7.56k | id, |
242 | 7.56k | types: Rc::new(types), |
243 | 7.56k | }; |
244 | | |
245 | | // This interface is defined at the package level, and it |
246 | | // must be unique. |
247 | 7.56k | let prev = ret |
248 | 7.56k | .file |
249 | 7.56k | .interfaces |
250 | 7.56k | .insert(interface.name.clone(), interface.clone()); |
251 | 7.56k | assert!(prev.is_none()); |
252 | 7.56k | let prev = ret.file.namespace.insert( |
253 | 7.56k | interface.name.clone(), |
254 | 7.56k | (DefinitionLevel::Package, DefinitionKind::Interface), |
255 | | ); |
256 | 7.56k | assert!(prev.is_none()); |
257 | | |
258 | | // This is also defined at the file level, and it must be |
259 | | // unique here too. |
260 | 7.56k | let prev = file |
261 | 7.56k | .interfaces |
262 | 7.56k | .insert(interface.name.clone(), interface.clone()); |
263 | 7.56k | assert!(prev.is_none()); |
264 | | |
265 | | // Insert the definition into all other files as well. |
266 | 18.1k | for file in files.iter_mut() { |
267 | 18.1k | if file.insert_definition(&interface.name, DefinitionKind::Interface) { |
268 | 10.5k | let prev = file |
269 | 10.5k | .interfaces |
270 | 10.5k | .insert(interface.name.clone(), interface.clone()); |
271 | 10.5k | assert!(prev.is_none()); |
272 | 7.57k | } |
273 | | } |
274 | | |
275 | 7.56k | empty = false; |
276 | | } |
277 | | Generate::Use => { |
278 | 4.67k | let mut piece = String::new(); |
279 | 4.67k | piece.push_str("use "); |
280 | 3.66k | let (name, id, types) = |
281 | 4.67k | match self.gen_interface_path(u, &mut ret.file, &mut piece)? { |
282 | 3.66k | Some(i) => i, |
283 | 1.00k | None => continue, |
284 | | }; |
285 | 3.66k | let name = name.to_string(); |
286 | 3.66k | let types = types.clone(); |
287 | | // If this interface's name already exist within this `file` |
288 | | // then this must be renamed with `as`. If the name exists |
289 | | // only at the package level then it's ok to replace it with |
290 | | // something else. |
291 | | // |
292 | | // If the name doesn't exist then use the fuzz input to |
293 | | // determine whether a rename should happen. |
294 | 3.66k | let name = |
295 | 3.66k | if matches!(file.namespace.get(&name), Some((DefinitionLevel::File, _))) |
296 | 1.79k | || u.arbitrary()? |
297 | | { |
298 | 3.32k | let name = file.gen_unique_file_name(u, DefinitionKind::Interface)?; |
299 | 3.32k | piece.push_str(" as %"); |
300 | 3.32k | piece.push_str(&name); |
301 | 3.32k | name |
302 | | } else { |
303 | 338 | file.namespace.insert( |
304 | 338 | name.clone(), |
305 | 338 | (DefinitionLevel::File, DefinitionKind::Interface), |
306 | | ); |
307 | 338 | name |
308 | | }; |
309 | 3.66k | piece.push_str(";"); |
310 | 3.66k | log::debug!("new use `{name}` in {i}"); |
311 | 3.66k | file.worlds.swap_remove(&name); |
312 | 3.66k | file.interfaces |
313 | 3.66k | .insert(name.clone(), FileInterface { name, id, types }); |
314 | 3.66k | file.items.push(piece) |
315 | | } |
316 | 1.46k | Generate::Done => break, |
317 | | }; |
318 | | } |
319 | | |
320 | 7.80k | shuffle(u, &mut files)?; |
321 | 18.3k | for file in files.iter_mut() { |
322 | 18.3k | shuffle(u, &mut file.items)?; |
323 | | } |
324 | | |
325 | 7.80k | let mut has_name = false; |
326 | 7.80k | let len = files.len(); |
327 | 18.3k | for (i, file) in files.iter_mut().enumerate() { |
328 | 18.3k | let mut s = String::new(); |
329 | 18.3k | if u.arbitrary()? || (!has_name && i == len - 1) { |
330 | 12.5k | has_name = true; |
331 | 12.5k | s.push_str("package "); |
332 | 12.5k | s.push_str("%"); |
333 | 12.5k | s.push_str(&ret.name.namespace); |
334 | 12.5k | s.push_str(":"); |
335 | 12.5k | s.push_str("%"); |
336 | 12.5k | s.push_str(&ret.name.name); |
337 | 12.5k | if let Some(version) = &ret.name.version { |
338 | 9.96k | s.push_str(&format!("@{version}")); |
339 | 9.96k | } |
340 | 12.5k | s.push_str(";\n\n"); |
341 | 5.79k | } |
342 | 45.2k | for piece in file.items.iter() { |
343 | 45.2k | s.push_str(&piece); |
344 | 45.2k | s.push_str("\n\n"); |
345 | 45.2k | } |
346 | 18.3k | log::trace!("==============================================="); |
347 | 18.3k | log::trace!("{s}"); |
348 | 18.3k | ret.sources.push(format!("wit{i}.wit").as_ref(), &s); |
349 | | } |
350 | 7.80k | Ok(ret) |
351 | 7.80k | } |
352 | | |
353 | 12.0k | fn gen_world( |
354 | 12.0k | &mut self, |
355 | 12.0k | u: &mut Unstructured<'_>, |
356 | 12.0k | name: &str, |
357 | 12.0k | file: &mut File, |
358 | 12.0k | package_name: &str, |
359 | 12.0k | version: Option<Version>, |
360 | 12.0k | ) -> Result<String> { |
361 | 12.0k | InterfaceGenerator::new(self, file, package_name, version).gen_world(u, name) |
362 | 12.0k | } |
363 | | |
364 | 29.5k | fn gen_interface( |
365 | 29.5k | &mut self, |
366 | 29.5k | u: &mut Unstructured<'_>, |
367 | 29.5k | name: Option<&str>, |
368 | 29.5k | file: &mut File, |
369 | 29.5k | package_name: &str, |
370 | 29.5k | world_name: Option<&str>, |
371 | 29.5k | version: Option<Version>, |
372 | 29.5k | ) -> Result<(String, Vec<Type>)> { |
373 | 29.5k | let mut generator = InterfaceGenerator::new(self, file, package_name, version); |
374 | 29.5k | let ret = generator.gen_interface(u, name, world_name)?; |
375 | 29.5k | Ok((ret, generator.types_in_interface)) |
376 | 29.5k | } |
377 | | |
378 | 111k | fn gen_interface_path<'a>( |
379 | 111k | &'a self, |
380 | 111k | u: &mut Unstructured<'_>, |
381 | 111k | file: &'a mut File, |
382 | 111k | dst: &mut String, |
383 | 111k | ) -> Result<Option<(&'a str, u32, &'a Rc<Vec<Type>>)>> { |
384 | | enum Choice { |
385 | | Interfaces, |
386 | | Packages, |
387 | | } |
388 | 111k | let mut choices = Vec::new(); |
389 | 111k | if !file.interfaces.is_empty() { |
390 | 46.4k | choices.push(Choice::Interfaces); |
391 | 64.7k | } |
392 | 111k | if !self.packages.packages_with_interfaces.is_empty() { |
393 | 38.7k | choices.push(Choice::Packages); |
394 | 72.4k | } |
395 | | |
396 | 111k | if choices.is_empty() { |
397 | 59.8k | return Ok(None); |
398 | 51.3k | } |
399 | 51.3k | Ok(match u.choose(&choices)? { |
400 | | Choice::Interfaces => { |
401 | 13.5k | let i = u.int_in_range(0..=file.interfaces.len() - 1)?; |
402 | 13.5k | let (name, i) = file.interfaces.get_index(i).unwrap(); |
403 | | // Once a name is used from a file's local namespace then it |
404 | | // can't be overridden in that namespace so switch it to a file |
405 | | // definition from whatever it previously was. |
406 | 13.5k | file.namespace.insert( |
407 | 13.5k | name.clone(), |
408 | 13.5k | (DefinitionLevel::File, DefinitionKind::Interface), |
409 | | ); |
410 | 13.5k | file.worlds.swap_remove(name); |
411 | 13.5k | dst.push_str("%"); |
412 | 13.5k | dst.push_str(&i.name); |
413 | 13.5k | Some((&i.name, i.id, &i.types)) |
414 | | } |
415 | | Choice::Packages => { |
416 | 37.8k | let pkg = u.choose(&self.packages.packages_with_interfaces)?; |
417 | 37.8k | let pkg = &self.packages.list[*pkg]; |
418 | 37.8k | dst.push_str("%"); |
419 | 37.8k | dst.push_str(&pkg.name.namespace); |
420 | 37.8k | dst.push_str(":"); |
421 | 37.8k | dst.push_str("%"); |
422 | 37.8k | dst.push_str(&pkg.name.name); |
423 | 37.8k | dst.push_str("/"); |
424 | 37.8k | let i = u.int_in_range(0..=pkg.file.interfaces.len() - 1)?; |
425 | 37.8k | let i = &pkg.file.interfaces[i]; |
426 | 37.8k | dst.push_str("%"); |
427 | 37.8k | dst.push_str(&i.name); |
428 | 37.8k | if let Some(version) = &pkg.name.version { |
429 | 35.8k | dst.push_str(&format!("@{version}")); |
430 | 35.8k | } |
431 | 37.8k | Some((&i.name, i.id, &i.types)) |
432 | | } |
433 | | }) |
434 | 111k | } |
435 | | |
436 | 0 | fn gen_world_path<'a>( |
437 | 0 | &'a self, |
438 | 0 | u: &mut Unstructured<'_>, |
439 | 0 | file: &'a mut File, |
440 | 0 | dst: &mut String, |
441 | 0 | includes: &mut HashSet<String>, |
442 | 0 | ) -> Result<WorldPath<'a>> { |
443 | | enum Choice { |
444 | | Worlds, |
445 | | Packages, |
446 | | } |
447 | 0 | let mut choices = Vec::new(); |
448 | 0 | if !file.worlds.is_empty() { |
449 | 0 | choices.push(Choice::Worlds); |
450 | 0 | } |
451 | 0 | if !self.packages.packages_with_worlds.is_empty() { |
452 | 0 | choices.push(Choice::Packages); |
453 | 0 | } |
454 | | |
455 | 0 | if choices.is_empty() { |
456 | 0 | return Ok(WorldPath::None); |
457 | 0 | } |
458 | 0 | Ok(match u.choose(&choices)? { |
459 | | Choice::Worlds => { |
460 | 0 | let i = u.int_in_range(0..=file.worlds.len() - 1)?; |
461 | 0 | let name = &file.worlds[i]; |
462 | | |
463 | 0 | if !includes.insert(name.to_string()) { |
464 | 0 | return Ok(WorldPath::None); |
465 | 0 | } |
466 | | |
467 | 0 | dst.push_str("%"); |
468 | 0 | dst.push_str(&name); |
469 | | // Same as `gen_interface_path`, once a name is used as a world |
470 | | // it's forced to always be a world so update its definition to |
471 | | // be a file-level world. |
472 | | |
473 | 0 | file.namespace |
474 | 0 | .insert(name.clone(), (DefinitionLevel::File, DefinitionKind::World)); |
475 | 0 | WorldPath::Local(name) |
476 | | } |
477 | | Choice::Packages => { |
478 | 0 | let pkg = u.choose(&self.packages.packages_with_worlds)?; |
479 | 0 | let pkg = &self.packages.list[*pkg]; |
480 | 0 | dst.push_str("%"); |
481 | 0 | dst.push_str(&pkg.name.namespace); |
482 | 0 | dst.push_str(":"); |
483 | 0 | dst.push_str("%"); |
484 | 0 | dst.push_str(&pkg.name.name); |
485 | 0 | dst.push_str("/"); |
486 | 0 | let i = u.int_in_range(0..=pkg.file.worlds.len() - 1)?; |
487 | 0 | let w = &pkg.file.worlds[i]; |
488 | 0 | dst.push_str("%"); |
489 | 0 | dst.push_str(&w); |
490 | 0 | if let Some(version) = &pkg.name.version { |
491 | 0 | dst.push_str(&format!("@{version}")); |
492 | 0 | } |
493 | 0 | WorldPath::Remote |
494 | | } |
495 | | }) |
496 | 0 | } |
497 | | } |
498 | | |
499 | | impl<'a> InterfaceGenerator<'a> { |
500 | 42.7k | fn new( |
501 | 42.7k | generator: &'a mut Generator, |
502 | 42.7k | file: &'a mut File, |
503 | 42.7k | package_name: &'a str, |
504 | 42.7k | version: Option<Version>, |
505 | 42.7k | ) -> InterfaceGenerator<'a> { |
506 | 42.7k | InterfaceGenerator { |
507 | 42.7k | generator, |
508 | 42.7k | file, |
509 | 42.7k | types_in_interface: Vec::new(), |
510 | 42.7k | // Claim the name `memory` to avoid conflicting with the canonical |
511 | 42.7k | // ABI always using a linear memory named `memory`. |
512 | 42.7k | unique_names: HashSet::from_iter(["memory".to_string()]), |
513 | 42.7k | package_name: package_name, |
514 | 42.7k | version, |
515 | 42.7k | } |
516 | 42.7k | } |
517 | | |
518 | | // Generate a feature gate annotation (@since, @unstable, or @deprecated) |
519 | | // If version is provided, ensures the annotation is compatible with the version |
520 | 185k | fn gen_feature_annotation(&self, u: &mut Unstructured<'_>) -> Result<Option<String>> { |
521 | 185k | if u.arbitrary()? { |
522 | 156k | return Ok(None); |
523 | 29.5k | } |
524 | | |
525 | 29.5k | let feature_names = ["active", "inactive"]; |
526 | | #[derive(Arbitrary)] |
527 | | enum AnnotationType { |
528 | | Since, |
529 | | Unstable, |
530 | | Deprecated, |
531 | | } |
532 | | |
533 | 29.5k | match self.version { |
534 | | None => { |
535 | | // No package version available |
536 | 25.4k | return Ok(None); |
537 | | } |
538 | 4.10k | Some(_) => match u.arbitrary()? { |
539 | | AnnotationType::Since => { |
540 | 1.73k | let v = gen_version_less_than(u, &self.version)?; |
541 | 1.73k | Ok(Some(format!("@since(version = {v})"))) |
542 | | } |
543 | | AnnotationType::Unstable => { |
544 | 1.52k | let feature = u.choose(&feature_names)?; |
545 | 1.52k | Ok(Some(format!("@unstable(feature = {feature})"))) |
546 | | } |
547 | | AnnotationType::Deprecated => { |
548 | 844 | let depreciation_version = gen_version_less_than(u, &self.version)?; |
549 | 844 | let since_version = |
550 | 844 | gen_version_less_than(u, &Some(depreciation_version.clone()))?; |
551 | 844 | Ok(Some(format!( |
552 | 844 | "@deprecated(version = {depreciation_version})\n@since(version = {since_version})", |
553 | 844 | ))) |
554 | | } |
555 | | }, |
556 | | } |
557 | 185k | } |
558 | | |
559 | 30.7k | fn gen_interface( |
560 | 30.7k | &mut self, |
561 | 30.7k | u: &mut Unstructured<'_>, |
562 | 30.7k | name: Option<&str>, |
563 | 30.7k | world_name: Option<&str>, |
564 | 30.7k | ) -> Result<String> { |
565 | 30.7k | let mut ret = String::new(); |
566 | | |
567 | 30.7k | if let Some(annotation) = self.gen_feature_annotation(u)? { |
568 | 0 | ret.push_str(&annotation); |
569 | 0 | ret.push_str("\n"); |
570 | 30.7k | } |
571 | | |
572 | 30.7k | ret.push_str("interface "); |
573 | 30.7k | if let Some(name) = name { |
574 | 29.5k | ret.push_str("%"); |
575 | 29.5k | ret.push_str(name); |
576 | 29.5k | ret.push_str(" "); |
577 | 29.5k | } |
578 | 30.7k | ret.push_str("{\n"); |
579 | | |
580 | | #[derive(Arbitrary)] |
581 | | enum Generate { |
582 | | Use, |
583 | | Type, |
584 | | Function, |
585 | | } |
586 | | |
587 | 30.7k | let mut parts = Vec::new(); |
588 | 134k | while parts.len() < self.generator.config.max_interface_items && u.arbitrary()? { |
589 | 103k | let mut part = String::new(); |
590 | 103k | if let Some(annotation) = self.gen_feature_annotation(u)? { |
591 | 0 | part.push_str(&annotation); |
592 | 0 | part.push_str("\n"); |
593 | 103k | } |
594 | | |
595 | 103k | if u.arbitrary()? { |
596 | 97.9k | part.push_str("@external-id(\"hi\")\n"); |
597 | 97.9k | } |
598 | | |
599 | 103k | match u.arbitrary()? { |
600 | | Generate::Use => { |
601 | 75.1k | if !self.gen_use(u, &mut part, world_name)? { |
602 | 54.5k | continue; |
603 | 20.6k | } |
604 | | } |
605 | | Generate::Type => { |
606 | 15.1k | let name = self.gen_unique_name(u)?; |
607 | 15.1k | let ty = self.gen_typedef(u, &name, &mut part)?; |
608 | 15.1k | let is_resource = ty.is_resource; |
609 | 15.1k | self.types_in_interface.push(ty); |
610 | 15.1k | if is_resource { |
611 | 1.14k | if u.arbitrary()? { |
612 | 972 | part.push_str(" {\n"); |
613 | 972 | self.gen_resource_funcs(&name, u, &mut part)?; |
614 | 972 | part.push_str("}"); |
615 | 175 | } else { |
616 | 175 | part.push_str(";"); |
617 | 175 | } |
618 | 14.0k | } |
619 | | } |
620 | | Generate::Function => { |
621 | 13.4k | self.gen_func(u, &mut part)?; |
622 | | } |
623 | | } |
624 | 49.2k | parts.push(part); |
625 | | } |
626 | | |
627 | 30.7k | shuffle(u, &mut parts)?; |
628 | 49.2k | for part in parts { |
629 | 49.2k | ret.push_str(&part); |
630 | 49.2k | ret.push_str("\n\n"); |
631 | 49.2k | } |
632 | | |
633 | 30.7k | ret.push_str("}"); |
634 | 30.7k | Ok(ret) |
635 | 30.7k | } |
636 | | |
637 | 12.0k | fn gen_world(&mut self, u: &mut Unstructured<'_>, world_name: &str) -> Result<String> { |
638 | 12.0k | let mut ret = String::new(); |
639 | 12.0k | ret.push_str("world %"); |
640 | 12.0k | ret.push_str(world_name); |
641 | 12.0k | ret.push_str(" {\n"); |
642 | | |
643 | | #[derive(Arbitrary, Copy, Clone, Debug)] |
644 | | enum Direction { |
645 | | Import, |
646 | | Export, |
647 | | } |
648 | | |
649 | | #[derive(Arbitrary)] |
650 | | enum ItemKind { |
651 | | Func(Direction), |
652 | | Interface(Direction), |
653 | | AnonInterface(Direction), |
654 | | ImplementsInterface(Direction), |
655 | | Type, |
656 | | Use, |
657 | | Include, |
658 | | } |
659 | | |
660 | 12.0k | let mut parts = Vec::new(); |
661 | 12.0k | let mut imported_interfaces = HashSet::new(); |
662 | 12.0k | let mut exported_interfaces = HashSet::new(); |
663 | 12.0k | let mut includes: HashSet<String> = HashSet::new(); |
664 | | |
665 | | // Claim the name `memory` to avoid conflicting with the canonical |
666 | | // ABI always using a linear memory named `memory`. |
667 | 12.0k | let mut export_names = HashSet::from_iter(["memory".to_string()]); |
668 | | |
669 | 60.5k | while parts.len() < self.generator.config.max_world_items |
670 | 55.0k | && !u.is_empty() |
671 | 53.0k | && u.arbitrary()? |
672 | | { |
673 | 48.4k | let kind = u.arbitrary::<ItemKind>()?; |
674 | | |
675 | | // Gate config-disabled features early, before consuming any |
676 | | // more random bytes, to keep byte consumption deterministic |
677 | | // when a feature is toggled off. |
678 | 48.4k | if matches!(kind, ItemKind::ImplementsInterface(_)) && !self.generator.config.implements |
679 | | { |
680 | 934 | continue; |
681 | 47.5k | } |
682 | | |
683 | 47.5k | let (direction, named) = match kind { |
684 | 4.01k | ItemKind::Func(dir) | ItemKind::AnonInterface(dir) => (Some(dir), true), |
685 | 29.7k | ItemKind::Interface(dir) | ItemKind::ImplementsInterface(dir) => (Some(dir), false), |
686 | 6.20k | ItemKind::Type => (None, true), |
687 | 1.67k | ItemKind::Use => (None, false), |
688 | 5.88k | ItemKind::Include => (None, false), |
689 | | }; |
690 | | |
691 | 47.5k | let mut part = String::new(); |
692 | | |
693 | 47.5k | if let Some(annotation) = self.gen_feature_annotation(u)? { |
694 | 4.00k | part.push_str(&annotation); |
695 | 4.00k | part.push_str("\n"); |
696 | 43.5k | } |
697 | | |
698 | 47.5k | if u.arbitrary()? { |
699 | 42.8k | part.push_str("@external-id(\"hi\")\n"); |
700 | 42.8k | } |
701 | | |
702 | 47.5k | if let Some(dir) = direction { |
703 | 33.7k | part.push_str(match dir { |
704 | 6.82k | Direction::Import => "import ", |
705 | 26.9k | Direction::Export => "export ", |
706 | | }); |
707 | 13.7k | } |
708 | | |
709 | 47.5k | let name = if named { |
710 | 10.2k | let names = match direction { |
711 | 8.46k | Some(Direction::Import) | None => &mut self.unique_names, |
712 | 1.76k | Some(Direction::Export) => &mut export_names, |
713 | | }; |
714 | 10.2k | let mut name = gen_unique_name(u, names)?; |
715 | | |
716 | | // check to see if any includes have a name clash, if so regenerate the name |
717 | | // this does have potential to throw away add a few names but that should be fine |
718 | 10.2k | for i in includes.iter() { |
719 | 0 | if self.generator.packages.contains_name( |
720 | 0 | self.package_name.to_string(), |
721 | 0 | i.to_string(), |
722 | 0 | &name, |
723 | | ) { |
724 | 0 | name = gen_unique_name(u, names)?; |
725 | 0 | } |
726 | | } |
727 | | |
728 | 10.2k | if direction.is_some() { |
729 | 4.01k | part.push_str("%"); |
730 | 4.01k | part.push_str(&name); |
731 | 4.01k | part.push_str(": "); |
732 | 6.20k | } |
733 | | |
734 | 10.2k | self.generator.packages.add_name( |
735 | 10.2k | self.package_name.to_string(), |
736 | 10.2k | world_name.to_string(), |
737 | 10.2k | name.to_string(), |
738 | | ); |
739 | 10.2k | Some(name) |
740 | | } else { |
741 | 37.3k | None |
742 | | }; |
743 | | |
744 | 47.5k | match kind { |
745 | | ItemKind::Func(_) => { |
746 | 2.77k | self.gen_func_sig(u, &mut part, false)?; |
747 | | } |
748 | 4.34k | ItemKind::Interface(dir) => { |
749 | 4.34k | let id = match self.generator.gen_interface_path(u, self.file, &mut part)? { |
750 | 2.41k | Some((_name, id, _types)) => id, |
751 | | // If an interface couldn't be chosen or wasn't |
752 | | // chosen then skip this import. A unique name was |
753 | | // selecteed above but we just sort of leave that |
754 | | // floating in the wild to get handled by some other |
755 | | // test case. |
756 | 1.93k | None => continue, |
757 | | }; |
758 | | |
759 | | // If this interface has already been imported or |
760 | | // exported this document can't do so again. Throw out |
761 | | // this item in that situation. |
762 | 2.41k | let unique = match dir { |
763 | 1.71k | Direction::Import => imported_interfaces.insert(id), |
764 | 696 | Direction::Export => exported_interfaces.insert(id), |
765 | | }; |
766 | 2.41k | if !unique { |
767 | 1.94k | continue; |
768 | 464 | } |
769 | 464 | part.push_str(";"); |
770 | | } |
771 | 25.4k | ItemKind::ImplementsInterface(dir) => { |
772 | 25.4k | let names = match dir { |
773 | 1.67k | Direction::Import => &mut self.unique_names, |
774 | 23.7k | Direction::Export => &mut export_names, |
775 | | }; |
776 | 25.4k | let label = gen_unique_name(u, names)?; |
777 | | |
778 | 25.4k | let mut path_str = String::new(); |
779 | 25.4k | if self |
780 | 25.4k | .generator |
781 | 25.4k | .gen_interface_path(u, self.file, &mut path_str)? |
782 | 25.4k | .is_none() |
783 | | { |
784 | 1.47k | continue; |
785 | 23.9k | } |
786 | | |
787 | 23.9k | self.generator.packages.add_name( |
788 | 23.9k | self.package_name.to_string(), |
789 | 23.9k | world_name.to_string(), |
790 | 23.9k | label.to_string(), |
791 | | ); |
792 | | |
793 | 23.9k | part.push_str("%"); |
794 | 23.9k | part.push_str(&label); |
795 | 23.9k | part.push_str(": "); |
796 | 23.9k | part.push_str(&path_str); |
797 | 23.9k | part.push_str(";"); |
798 | | } |
799 | | ItemKind::AnonInterface(_) => { |
800 | 1.24k | let iface = |
801 | 1.24k | InterfaceGenerator::new(self.generator, self.file, self.package_name, None) |
802 | 1.24k | .gen_interface(u, None, Some(world_name))?; |
803 | 1.24k | part.push_str(&iface); |
804 | | } |
805 | | |
806 | | ItemKind::Type => { |
807 | 6.20k | let name = name.unwrap(); |
808 | 6.20k | let ty = self.gen_typedef(u, &name, &mut part)?; |
809 | 6.20k | let is_resource = ty.is_resource; |
810 | 6.20k | self.types_in_interface.push(ty); |
811 | | |
812 | 6.20k | if is_resource { |
813 | 504 | if u.arbitrary()? { |
814 | 426 | part.push_str(" {\n"); |
815 | 426 | self.gen_resource_funcs(&name, u, &mut part)?; |
816 | 426 | part.push_str("}"); |
817 | 78 | } else { |
818 | 78 | part.push_str(";"); |
819 | 78 | } |
820 | 5.70k | } |
821 | | } |
822 | | |
823 | | ItemKind::Use => { |
824 | 1.67k | if !self.gen_use(u, &mut part, Some(world_name))? { |
825 | 962 | continue; |
826 | 714 | } |
827 | | } |
828 | | |
829 | | ItemKind::Include => { |
830 | 5.88k | if !self.generator.config.world_include { |
831 | 5.88k | continue; |
832 | 0 | } |
833 | 0 | part.push_str("include "); |
834 | 0 | match self |
835 | 0 | .generator |
836 | 0 | .gen_world_path(u, self.file, &mut part, &mut includes)? |
837 | | { |
838 | 0 | WorldPath::Local(name) => { |
839 | | // rename things if there is an naming conflict with |
840 | | // the include and the world we are going into this |
841 | | // is a best effort, there are some edge cases where |
842 | | // we might not catch something in that case we just |
843 | | // throw away the generated world for fuzzing |
844 | 0 | let current_world = PackageWorldKey { |
845 | 0 | package_name: self.package_name.to_owned(), |
846 | 0 | world_name: world_name.to_owned(), |
847 | 0 | }; |
848 | 0 | let include_world = PackageWorldKey { |
849 | 0 | package_name: self.package_name.to_owned(), |
850 | 0 | world_name: name.to_owned(), |
851 | 0 | }; |
852 | 0 | let intersection = self |
853 | 0 | .generator |
854 | 0 | .packages |
855 | 0 | .intersect(current_world, include_world); |
856 | 0 | if let Some(names) = intersection { |
857 | 0 | part.push_str(" with { %"); |
858 | | |
859 | 0 | for n in names { |
860 | 0 | part.push_str(n); |
861 | 0 | part.push_str(" as %"); |
862 | | // we know it is in one of the worlds, lets |
863 | | // add it here just for good measure |
864 | 0 | self.unique_names.insert(n.to_string()); |
865 | 0 | let new_name = gen_unique_name(u, &mut self.unique_names)?; |
866 | 0 | part.push_str(&new_name); |
867 | 0 | part.push_str(","); |
868 | | } |
869 | 0 | part.push_str("}"); |
870 | 0 | } else { |
871 | 0 | // ; is only used if not renaming |
872 | 0 | part.push_str(";"); |
873 | 0 | } |
874 | | } |
875 | 0 | WorldPath::Remote => { |
876 | 0 | part.push_str(";"); |
877 | 0 | } |
878 | 0 | WorldPath::None => continue, |
879 | | }; |
880 | | } |
881 | | } |
882 | 35.3k | parts.push(part); |
883 | | } |
884 | | |
885 | 12.0k | shuffle(u, &mut parts)?; |
886 | | |
887 | 35.3k | for part in parts { |
888 | 35.3k | ret.push_str(&part); |
889 | 35.3k | ret.push_str("\n"); |
890 | 35.3k | } |
891 | | |
892 | 12.0k | ret.push_str("}"); |
893 | | |
894 | 12.0k | Ok(ret) |
895 | 12.0k | } |
896 | | |
897 | 1.39k | fn gen_resource_funcs( |
898 | 1.39k | &mut self, |
899 | 1.39k | resource_name: &str, |
900 | 1.39k | u: &mut Unstructured<'_>, |
901 | 1.39k | ret: &mut String, |
902 | 1.39k | ) -> Result<()> { |
903 | 1.39k | let mut parts = Vec::new(); |
904 | | |
905 | | #[derive(Arbitrary)] |
906 | | enum Item { |
907 | | Constructor, |
908 | | Static, |
909 | | Method, |
910 | | } |
911 | | |
912 | 1.39k | let mut has_constructor = false; |
913 | 1.39k | let mut names = HashSet::new(); |
914 | 1.39k | names.insert(resource_name.to_string()); |
915 | 5.38k | while parts.len() < self.generator.config.max_resource_items |
916 | 4.92k | && !u.is_empty() |
917 | 4.76k | && u.arbitrary()? |
918 | | { |
919 | 3.99k | match u.arbitrary()? { |
920 | 466 | Item::Constructor if has_constructor => {} |
921 | | Item::Constructor => { |
922 | 455 | has_constructor = true; |
923 | 455 | let mut part = String::new(); |
924 | | |
925 | 455 | if let Some(annotation) = self.gen_feature_annotation(u)? { |
926 | 34 | part.push_str(&annotation); |
927 | 34 | part.push_str("\n"); |
928 | 421 | } |
929 | | |
930 | 455 | part.push_str("constructor"); |
931 | 455 | self.gen_params(u, &mut part, false)?; |
932 | 455 | part.push_str(";"); |
933 | 455 | parts.push(part); |
934 | | } |
935 | | Item::Static => { |
936 | 1.15k | let mut part = String::new(); |
937 | | |
938 | 1.15k | if let Some(annotation) = self.gen_feature_annotation(u)? { |
939 | 16 | part.push_str(&annotation); |
940 | 16 | part.push_str("\n"); |
941 | 1.13k | } |
942 | | |
943 | 1.15k | part.push_str("%"); |
944 | 1.15k | part.push_str(&gen_unique_name(u, &mut names)?); |
945 | 1.15k | part.push_str(": static "); |
946 | 1.15k | self.gen_func_sig(u, &mut part, false)?; |
947 | 1.15k | parts.push(part); |
948 | | } |
949 | | Item::Method => { |
950 | 1.92k | let mut part = String::new(); |
951 | | |
952 | 1.92k | if let Some(annotation) = self.gen_feature_annotation(u)? { |
953 | 45 | part.push_str(&annotation); |
954 | 45 | part.push_str("\n"); |
955 | 1.87k | } |
956 | | |
957 | 1.92k | part.push_str("%"); |
958 | 1.92k | part.push_str(&gen_unique_name(u, &mut names)?); |
959 | 1.92k | part.push_str(": "); |
960 | 1.92k | self.gen_func_sig(u, &mut part, true)?; |
961 | 1.92k | parts.push(part); |
962 | | } |
963 | | } |
964 | | } |
965 | | |
966 | 1.39k | shuffle(u, &mut parts)?; |
967 | | |
968 | 3.52k | for part in parts { |
969 | 3.52k | ret.push_str(&part); |
970 | 3.52k | ret.push_str("\n"); |
971 | 3.52k | } |
972 | 1.39k | Ok(()) |
973 | 1.39k | } |
974 | | |
975 | 76.8k | fn gen_use( |
976 | 76.8k | &mut self, |
977 | 76.8k | u: &mut Unstructured<'_>, |
978 | 76.8k | part: &mut String, |
979 | 76.8k | world_name: Option<&str>, |
980 | 76.8k | ) -> Result<bool> { |
981 | 76.8k | let mut path = String::new(); |
982 | 21.3k | let (_name, _id, types) = |
983 | 76.8k | match self.generator.gen_interface_path(u, self.file, &mut path)? { |
984 | 21.3k | Some(types) => types, |
985 | 55.4k | None => return Ok(false), |
986 | | }; |
987 | 21.3k | part.push_str("use "); |
988 | 21.3k | part.push_str(&path); |
989 | 21.3k | part.push_str(".{"); |
990 | 21.3k | let ty = u.choose(types)?; |
991 | 21.3k | part.push_str("%"); |
992 | 21.3k | part.push_str(&ty.name); |
993 | 21.3k | let size = ty.size; |
994 | 21.3k | let is_resource = ty.is_resource; |
995 | 21.3k | let name = if self.unique_names.contains(&ty.name) || u.arbitrary()? { |
996 | 20.0k | part.push_str(" as %"); |
997 | 20.0k | let name = self.gen_unique_name(u)?; |
998 | 20.0k | part.push_str(&name); |
999 | | // if we name something then we need track it at the package level for includes |
1000 | 20.0k | if let Some(world_name) = world_name { |
1001 | 1.10k | self.generator.packages.add_name( |
1002 | 1.10k | self.package_name.to_string(), |
1003 | 1.10k | world_name.to_string(), |
1004 | 1.10k | name.to_string(), |
1005 | 1.10k | ); |
1006 | 18.9k | } |
1007 | 20.0k | name |
1008 | | } else { |
1009 | 1.33k | assert!(self.unique_names.insert(ty.name.clone())); |
1010 | 1.33k | ty.name.clone() |
1011 | | }; |
1012 | 21.3k | self.types_in_interface.push(Type { |
1013 | 21.3k | name, |
1014 | 21.3k | size, |
1015 | 21.3k | is_resource, |
1016 | 21.3k | }); |
1017 | 21.3k | part.push_str("};"); |
1018 | 21.3k | Ok(true) |
1019 | 76.8k | } |
1020 | | |
1021 | 21.3k | fn gen_typedef( |
1022 | 21.3k | &mut self, |
1023 | 21.3k | u: &mut Unstructured<'_>, |
1024 | 21.3k | name: &str, |
1025 | 21.3k | ret: &mut String, |
1026 | 21.3k | ) -> Result<Type> { |
1027 | | #[derive(Arbitrary)] |
1028 | | pub enum Kind { |
1029 | | Record, |
1030 | | Flags, |
1031 | | Variant, |
1032 | | Enum, |
1033 | | Anonymous, |
1034 | | Resource, |
1035 | | } |
1036 | | |
1037 | 21.3k | let mut fuel = self.generator.config.max_type_size; |
1038 | | |
1039 | 21.3k | let mut is_resource = false; |
1040 | 21.3k | match u.arbitrary()? { |
1041 | | Kind::Record => { |
1042 | 1.78k | ret.push_str("record %"); |
1043 | 1.78k | ret.push_str(name); |
1044 | 1.78k | ret.push_str(" {\n"); |
1045 | 1.78k | for _ in 0..u.int_in_range(1..=self.generator.config.max_type_parts)? { |
1046 | 5.51k | ret.push_str(" %"); |
1047 | 5.51k | ret.push_str(&self.gen_unique_name(u)?); |
1048 | 5.51k | ret.push_str(": "); |
1049 | 5.51k | self.gen_type(u, &mut fuel, 0, ret)?; |
1050 | 5.51k | ret.push_str(",\n"); |
1051 | | } |
1052 | 1.78k | ret.push_str("}"); |
1053 | | } |
1054 | | Kind::Variant => { |
1055 | 4.86k | ret.push_str("variant %"); |
1056 | 4.86k | ret.push_str(name); |
1057 | 4.86k | ret.push_str(" {\n"); |
1058 | 4.86k | for _ in 0..u.int_in_range(1..=self.generator.config.max_type_parts)? { |
1059 | 16.1k | ret.push_str(" %"); |
1060 | 16.1k | ret.push_str(&self.gen_unique_name(u)?); |
1061 | 16.1k | if u.arbitrary()? { |
1062 | 14.8k | ret.push_str("("); |
1063 | 14.8k | self.gen_type(u, &mut fuel, 0, ret)?; |
1064 | 14.8k | ret.push_str(")"); |
1065 | 1.29k | } |
1066 | 16.1k | ret.push_str(",\n"); |
1067 | | } |
1068 | 4.86k | ret.push_str("}"); |
1069 | | } |
1070 | | Kind::Enum => { |
1071 | 10.1k | ret.push_str("enum %"); |
1072 | 10.1k | ret.push_str(name); |
1073 | 10.1k | ret.push_str(" {\n"); |
1074 | 10.1k | for _ in 0..u.int_in_range(1..=self.generator.config.max_type_parts)? { |
1075 | 49.1k | ret.push_str(" %"); |
1076 | 49.1k | ret.push_str(&self.gen_unique_name(u)?); |
1077 | 49.1k | ret.push_str(",\n"); |
1078 | | } |
1079 | 10.1k | ret.push_str("}"); |
1080 | | } |
1081 | | Kind::Flags => { |
1082 | 979 | ret.push_str("flags %"); |
1083 | 979 | ret.push_str(name); |
1084 | 979 | ret.push_str(" {\n"); |
1085 | 979 | for _ in 0..u.int_in_range(1..=self.generator.config.max_type_parts)? { |
1086 | 2.62k | ret.push_str(" %"); |
1087 | 2.62k | ret.push_str(&self.gen_unique_name(u)?); |
1088 | 2.62k | ret.push_str(",\n"); |
1089 | | } |
1090 | 979 | ret.push_str("}"); |
1091 | | } |
1092 | | Kind::Anonymous => { |
1093 | 1.95k | ret.push_str("type %"); |
1094 | 1.95k | ret.push_str(name); |
1095 | 1.95k | ret.push_str(" = "); |
1096 | 1.95k | self.gen_type(u, &mut fuel, 0, ret)?; |
1097 | 1.95k | ret.push_str(";"); |
1098 | | } |
1099 | 1.65k | Kind::Resource => { |
1100 | 1.65k | is_resource = true; |
1101 | 1.65k | ret.push_str("resource %"); |
1102 | 1.65k | ret.push_str(name); |
1103 | 1.65k | } |
1104 | | } |
1105 | | |
1106 | 21.3k | Ok(Type { |
1107 | 21.3k | size: self.generator.config.max_type_size - fuel, |
1108 | 21.3k | is_resource, |
1109 | 21.3k | name: name.to_string(), |
1110 | 21.3k | }) |
1111 | 21.3k | } |
1112 | | |
1113 | 333k | fn gen_type( |
1114 | 333k | &mut self, |
1115 | 333k | u: &mut Unstructured<'_>, |
1116 | 333k | fuel: &mut usize, |
1117 | 333k | depth: usize, |
1118 | 333k | dst: &mut String, |
1119 | 333k | ) -> Result<()> { |
1120 | | #[derive(Arbitrary)] |
1121 | | enum Kind { |
1122 | | Bool, |
1123 | | U8, |
1124 | | U16, |
1125 | | U32, |
1126 | | U64, |
1127 | | S8, |
1128 | | S16, |
1129 | | S32, |
1130 | | S64, |
1131 | | F32, |
1132 | | F64, |
1133 | | Char, |
1134 | | String, |
1135 | | Id, |
1136 | | Tuple, |
1137 | | Option, |
1138 | | Result, |
1139 | | List, |
1140 | | FixedLengthList, |
1141 | | Stream, |
1142 | | Future, |
1143 | | ErrorContext, |
1144 | | } |
1145 | | |
1146 | | // Bail out with a leaf type if a nested type here would nest too |
1147 | | // deeply. Note that this is intentionally smaller than wasmparser's |
1148 | | // current limit of 100 to give some wiggle room to the generator here |
1149 | | // to be off-by-one. |
1150 | | const MAX_DEPTH: usize = 50; |
1151 | 333k | if depth + 1 >= MAX_DEPTH { |
1152 | 13.5k | dst.push_str("bool"); |
1153 | 13.5k | return Ok(()); |
1154 | 319k | } |
1155 | | |
1156 | 319k | *fuel = match fuel.checked_sub(1) { |
1157 | 280k | Some(fuel) => fuel, |
1158 | | None => { |
1159 | 39.4k | dst.push_str("bool"); |
1160 | 39.4k | return Ok(()); |
1161 | | } |
1162 | | }; |
1163 | | loop { |
1164 | 303k | break match u.arbitrary()? { |
1165 | 19.6k | Kind::Bool => dst.push_str("bool"), |
1166 | 6.84k | Kind::U8 => dst.push_str("u8"), |
1167 | 6.87k | Kind::S8 => dst.push_str("s8"), |
1168 | 1.98k | Kind::U16 => dst.push_str("u16"), |
1169 | 1.84k | Kind::S16 => dst.push_str("s16"), |
1170 | 2.18k | Kind::U32 => dst.push_str("u32"), |
1171 | 2.17k | Kind::S32 => dst.push_str("s32"), |
1172 | 1.95k | Kind::U64 => dst.push_str("u64"), |
1173 | 8.11k | Kind::S64 => dst.push_str("s64"), |
1174 | 4.20k | Kind::F32 => dst.push_str("f32"), |
1175 | 6.21k | Kind::F64 => dst.push_str("f64"), |
1176 | 14.0k | Kind::Char => dst.push_str("char"), |
1177 | 1.60k | Kind::String => dst.push_str("string"), |
1178 | | Kind::Id => { |
1179 | 4.63k | if self.types_in_interface.is_empty() { |
1180 | 2.73k | continue; |
1181 | 1.90k | } |
1182 | 1.90k | let ty = u.choose(&self.types_in_interface)?; |
1183 | 1.90k | *fuel = match fuel.checked_sub(ty.size) { |
1184 | 1.84k | Some(fuel) => fuel, |
1185 | 57 | None => continue, |
1186 | | }; |
1187 | 1.84k | let own_wrapper = if ty.is_resource && u.arbitrary()? { |
1188 | 355 | dst.push_str("own<"); |
1189 | 355 | true |
1190 | | } else { |
1191 | 1.48k | false |
1192 | | }; |
1193 | 1.84k | dst.push_str("%"); |
1194 | 1.84k | dst.push_str(&ty.name); |
1195 | 1.84k | if own_wrapper { |
1196 | 355 | dst.push_str(">"); |
1197 | 1.48k | } |
1198 | | } |
1199 | | Kind::Tuple => { |
1200 | 24.1k | let fields = u.int_in_range(1..=self.generator.config.max_type_parts)?; |
1201 | 24.1k | *fuel = match fuel.checked_sub(fields) { |
1202 | 22.4k | Some(fuel) => fuel, |
1203 | 1.67k | None => continue, |
1204 | | }; |
1205 | 22.4k | dst.push_str("tuple<"); |
1206 | 83.2k | for i in 0..fields { |
1207 | 83.2k | if i > 0 { |
1208 | 60.7k | dst.push_str(", "); |
1209 | 60.7k | } |
1210 | 83.2k | self.gen_type(u, fuel, depth + 1, dst)?; |
1211 | | } |
1212 | 22.4k | dst.push_str(">"); |
1213 | | } |
1214 | | Kind::Option => { |
1215 | 25.4k | *fuel = match fuel.checked_sub(1) { |
1216 | 24.9k | Some(fuel) => fuel, |
1217 | 519 | None => continue, |
1218 | | }; |
1219 | 24.9k | dst.push_str("option<"); |
1220 | 24.9k | self.gen_type(u, fuel, depth + 1, dst)?; |
1221 | 24.9k | dst.push_str(">"); |
1222 | | } |
1223 | | Kind::List => { |
1224 | 4.59k | *fuel = match fuel.checked_sub(1) { |
1225 | 4.20k | Some(fuel) => fuel, |
1226 | 391 | None => continue, |
1227 | | }; |
1228 | 4.20k | dst.push_str("list<"); |
1229 | 4.20k | self.gen_type(u, fuel, depth + 1, dst)?; |
1230 | 4.20k | dst.push_str(">"); |
1231 | | } |
1232 | | Kind::FixedLengthList => { |
1233 | 96.3k | if !self.generator.config.fixed_length_lists { |
1234 | 929 | continue; |
1235 | 95.4k | } |
1236 | 95.4k | *fuel = match fuel.checked_sub(1) { |
1237 | 95.4k | Some(fuel) => fuel, |
1238 | 35 | None => continue, |
1239 | | }; |
1240 | 95.4k | let elements = |
1241 | 95.4k | u.int_in_range(1..=self.generator.config.max_type_parts as u32)?; |
1242 | 95.4k | dst.push_str("list<"); |
1243 | 95.4k | self.gen_type(u, fuel, depth + 1, dst)?; |
1244 | 95.4k | dst.push_str(&format!(", {elements}>")); |
1245 | | } |
1246 | | Kind::Result => { |
1247 | 13.6k | *fuel = match fuel.checked_sub(2) { |
1248 | 13.0k | Some(fuel) => fuel, |
1249 | 566 | None => continue, |
1250 | | }; |
1251 | 13.0k | dst.push_str("result"); |
1252 | 13.0k | let ok = u.arbitrary()?; |
1253 | 13.0k | let err = u.arbitrary()?; |
1254 | 13.0k | match (ok, err) { |
1255 | | (true, true) => { |
1256 | 9.95k | dst.push_str("<"); |
1257 | 9.95k | self.gen_type(u, fuel, depth + 1, dst)?; |
1258 | 9.95k | dst.push_str(", "); |
1259 | 9.95k | self.gen_type(u, fuel, depth + 1, dst)?; |
1260 | 9.95k | dst.push_str(">"); |
1261 | | } |
1262 | | (true, false) => { |
1263 | 1.83k | dst.push_str("<"); |
1264 | 1.83k | self.gen_type(u, fuel, depth + 1, dst)?; |
1265 | 1.83k | dst.push_str(">"); |
1266 | | } |
1267 | | (false, true) => { |
1268 | 1.06k | dst.push_str("<_, "); |
1269 | 1.06k | self.gen_type(u, fuel, depth + 1, dst)?; |
1270 | 1.06k | dst.push_str(">"); |
1271 | | } |
1272 | 251 | (false, false) => {} |
1273 | | } |
1274 | | } |
1275 | | Kind::Stream => { |
1276 | 5.56k | if !self.generator.config.streams { |
1277 | 1.68k | continue; |
1278 | 3.88k | } |
1279 | 3.88k | *fuel = match fuel.checked_sub(1) { |
1280 | 3.76k | Some(fuel) => fuel, |
1281 | 123 | None => continue, |
1282 | | }; |
1283 | 3.76k | dst.push_str("stream<"); |
1284 | 3.76k | self.gen_type(u, fuel, depth + 1, dst)?; |
1285 | 3.76k | dst.push_str(">"); |
1286 | | } |
1287 | | Kind::Future => { |
1288 | 13.5k | if !self.generator.config.futures { |
1289 | 1.22k | continue; |
1290 | 12.3k | } |
1291 | 12.3k | *fuel = match fuel.checked_sub(1) { |
1292 | 12.2k | Some(fuel) => fuel, |
1293 | 73 | None => continue, |
1294 | | }; |
1295 | 12.2k | if u.arbitrary()? { |
1296 | 10.7k | dst.push_str("future<"); |
1297 | 10.7k | self.gen_type(u, fuel, depth + 1, dst)?; |
1298 | 10.7k | dst.push_str(">"); |
1299 | 1.45k | } else { |
1300 | 1.45k | dst.push_str("future"); |
1301 | 1.45k | } |
1302 | | } |
1303 | | Kind::ErrorContext => { |
1304 | 37.7k | if !self.generator.config.error_context { |
1305 | 12.9k | continue; |
1306 | 24.8k | } |
1307 | 24.8k | dst.push_str("error-context"); |
1308 | | } |
1309 | | }; |
1310 | | } |
1311 | | |
1312 | 280k | Ok(()) |
1313 | 333k | } |
1314 | | |
1315 | 13.4k | fn gen_func(&mut self, u: &mut Unstructured<'_>, ret: &mut String) -> Result<()> { |
1316 | 13.4k | ret.push_str("%"); |
1317 | 13.4k | ret.push_str(&self.gen_unique_name(u)?); |
1318 | 13.4k | ret.push_str(": "); |
1319 | 13.4k | self.gen_func_sig(u, ret, false)?; |
1320 | 13.4k | Ok(()) |
1321 | 13.4k | } |
1322 | | |
1323 | 19.3k | fn gen_func_sig( |
1324 | 19.3k | &mut self, |
1325 | 19.3k | u: &mut Unstructured<'_>, |
1326 | 19.3k | dst: &mut String, |
1327 | 19.3k | method: bool, |
1328 | 19.3k | ) -> Result<()> { |
1329 | 19.3k | if self.generator.config.async_ && u.arbitrary()? { |
1330 | 12.0k | dst.push_str("async "); |
1331 | 12.0k | } |
1332 | 19.3k | dst.push_str("func"); |
1333 | 19.3k | self.gen_params(u, dst, method)?; |
1334 | 19.3k | if u.arbitrary()? { |
1335 | 15.3k | dst.push_str(" -> "); |
1336 | 15.3k | let mut fuel = self.generator.config.max_type_size; |
1337 | 15.3k | self.gen_type(u, &mut fuel, 0, dst)?; |
1338 | 3.97k | } |
1339 | 19.3k | dst.push_str(";"); |
1340 | 19.3k | Ok(()) |
1341 | 19.3k | } |
1342 | | |
1343 | 19.7k | fn gen_params( |
1344 | 19.7k | &mut self, |
1345 | 19.7k | u: &mut Unstructured<'_>, |
1346 | 19.7k | dst: &mut String, |
1347 | 19.7k | method: bool, |
1348 | 19.7k | ) -> Result<()> { |
1349 | 19.7k | dst.push_str("("); |
1350 | 19.7k | let mut names = HashSet::new(); |
1351 | 19.7k | if method { |
1352 | 1.92k | names.insert("self".to_string()); |
1353 | 17.8k | } |
1354 | 19.7k | let mut fuel = self.generator.config.max_type_size; |
1355 | 50.7k | for i in 0..u.int_in_range(0..=self.generator.config.max_type_parts)? { |
1356 | 50.7k | if i > 0 { |
1357 | 34.0k | dst.push_str(", "); |
1358 | 34.0k | } |
1359 | 50.7k | dst.push_str("%"); |
1360 | 50.7k | dst.push_str(&gen_unique_name(u, &mut names)?); |
1361 | 50.7k | dst.push_str(": "); |
1362 | 50.7k | self.gen_type(u, &mut fuel, 0, dst)?; |
1363 | | } |
1364 | 19.7k | dst.push_str(")"); |
1365 | 19.7k | Ok(()) |
1366 | 19.7k | } |
1367 | | |
1368 | 122k | fn gen_unique_name(&mut self, u: &mut Unstructured<'_>) -> Result<String> { |
1369 | 122k | gen_unique_name(u, &mut self.unique_names) |
1370 | 122k | } |
1371 | | } |
1372 | | |
1373 | 227k | fn gen_unique_name(u: &mut Unstructured<'_>, set: &mut HashSet<String>) -> Result<String> { |
1374 | 227k | let mut name = gen_name(u)?; |
1375 | 387k | while !set.insert(name.clone()) { |
1376 | 160k | write!(&mut name, "{}", set.len()).unwrap(); |
1377 | 160k | } |
1378 | 227k | Ok(name) |
1379 | 227k | } |
1380 | | |
1381 | 272k | fn gen_name(u: &mut Unstructured<'_>) -> Result<String> { |
1382 | 272k | let size = u.arbitrary_len::<u8>()?; |
1383 | 272k | let size = std::cmp::min(size, 20); |
1384 | 272k | let name = match str::from_utf8(u.peek_bytes(size).unwrap()) { |
1385 | 76.7k | Ok(s) => { |
1386 | 76.7k | u.bytes(size).unwrap(); |
1387 | 76.7k | s.to_string() |
1388 | | } |
1389 | 195k | Err(e) => { |
1390 | 195k | let i = e.valid_up_to(); |
1391 | 195k | let valid = u.bytes(i).unwrap(); |
1392 | 195k | str::from_utf8(valid).unwrap().to_string() |
1393 | | } |
1394 | | }; |
1395 | 272k | let name = name |
1396 | 272k | .chars() |
1397 | 982k | .map(|x| if x.is_ascii_lowercase() { x } else { 'x' }) |
1398 | 272k | .collect::<String>(); |
1399 | 272k | Ok(if name.is_empty() { |
1400 | 188k | "name".to_string() |
1401 | | } else { |
1402 | 83.5k | name |
1403 | | }) |
1404 | 272k | } |
1405 | | |
1406 | 70.3k | fn shuffle<T>(u: &mut Unstructured<'_>, mut slice: &mut [T]) -> Result<()> { |
1407 | 222k | while slice.len() > 0 { |
1408 | 151k | let pos = u.int_in_range(0..=slice.len() - 1)?; |
1409 | 151k | slice.swap(0, pos); |
1410 | 151k | slice = &mut slice[1..]; |
1411 | | } |
1412 | 70.3k | Ok(()) |
1413 | 70.3k | } wit_smith::generate::shuffle::<wit_smith::generate::File> Line | Count | Source | 1406 | 7.80k | fn shuffle<T>(u: &mut Unstructured<'_>, mut slice: &mut [T]) -> Result<()> { | 1407 | 26.1k | while slice.len() > 0 { | 1408 | 18.3k | let pos = u.int_in_range(0..=slice.len() - 1)?; | 1409 | 18.3k | slice.swap(0, pos); | 1410 | 18.3k | slice = &mut slice[1..]; | 1411 | | } | 1412 | 7.80k | Ok(()) | 1413 | 7.80k | } |
wit_smith::generate::shuffle::<alloc::string::String> Line | Count | Source | 1406 | 62.5k | fn shuffle<T>(u: &mut Unstructured<'_>, mut slice: &mut [T]) -> Result<()> { | 1407 | 195k | while slice.len() > 0 { | 1408 | 133k | let pos = u.int_in_range(0..=slice.len() - 1)?; | 1409 | 133k | slice.swap(0, pos); | 1410 | 133k | slice = &mut slice[1..]; | 1411 | | } | 1412 | 62.5k | Ok(()) | 1413 | 62.5k | } |
|
1414 | | |
1415 | | #[derive(Clone, Default)] |
1416 | | struct File { |
1417 | | items: Vec<String>, |
1418 | | namespace: HashMap<String, (DefinitionLevel, DefinitionKind)>, |
1419 | | interfaces: IndexMap<String, FileInterface>, |
1420 | | worlds: IndexSet<String>, |
1421 | | } |
1422 | | |
1423 | | #[derive(Clone)] |
1424 | | struct FileInterface { |
1425 | | name: String, |
1426 | | id: u32, |
1427 | | types: Rc<Vec<Type>>, |
1428 | | } |
1429 | | |
1430 | | #[derive(Debug, Copy, Clone, PartialEq)] |
1431 | | enum DefinitionLevel { |
1432 | | Package, |
1433 | | File, |
1434 | | } |
1435 | | |
1436 | | #[derive(Debug, Copy, Clone, PartialEq)] |
1437 | | enum DefinitionKind { |
1438 | | World, |
1439 | | Interface, |
1440 | | } |
1441 | | |
1442 | | enum WorldPath<'a> { |
1443 | | None, |
1444 | | Local(&'a str), |
1445 | | Remote, |
1446 | | } |
1447 | | |
1448 | | impl File { |
1449 | 41.5k | fn gen_unique_package_name( |
1450 | 41.5k | &mut self, |
1451 | 41.5k | u: &mut Unstructured<'_>, |
1452 | 41.5k | names: &mut HashSet<String>, |
1453 | 41.5k | kind: DefinitionKind, |
1454 | 41.5k | ) -> Result<String> { |
1455 | 41.5k | let mut name = gen_name(u)?; |
1456 | | loop { |
1457 | | // Find a package-unique name first |
1458 | 69.3k | if !names.insert(name.clone()) { |
1459 | 27.7k | write!(&mut name, "{}", names.len()).unwrap(); |
1460 | 27.7k | continue; |
1461 | 41.5k | } |
1462 | | |
1463 | | // Then make sure it's file-unique too |
1464 | 41.5k | if self.claim_file_name(&mut name, kind) { |
1465 | 41.5k | break; |
1466 | 30 | } |
1467 | | } |
1468 | 41.5k | Ok(name) |
1469 | 41.5k | } |
1470 | | |
1471 | 3.32k | fn gen_unique_file_name( |
1472 | 3.32k | &mut self, |
1473 | 3.32k | u: &mut Unstructured<'_>, |
1474 | 3.32k | kind: DefinitionKind, |
1475 | 3.32k | ) -> Result<String> { |
1476 | 3.32k | let mut name = gen_name(u)?; |
1477 | 5.04k | while !self.claim_file_name(&mut name, kind) { |
1478 | 1.71k | // try again on the next iteration |
1479 | 1.71k | } |
1480 | 3.32k | Ok(name) |
1481 | 3.32k | } |
1482 | | |
1483 | 46.6k | fn claim_file_name(&mut self, name: &mut String, kind: DefinitionKind) -> bool { |
1484 | 46.6k | match self.namespace.entry(name.clone()) { |
1485 | 2.01k | Entry::Occupied(mut e) => match e.get().0 { |
1486 | | // If this name is already claimed elsewhere in the package |
1487 | | // then that's ok as we're going to shadow it, so switch it |
1488 | | // to a file definition. |
1489 | 268 | DefinitionLevel::Package => *e.get_mut() = (DefinitionLevel::File, kind), |
1490 | | |
1491 | | // If it's already defined in the file try to add more stuff |
1492 | | // to the name to make the next try not collide. |
1493 | | DefinitionLevel::File => { |
1494 | 1.74k | name.push_str("y"); |
1495 | 1.74k | write!(name, "{}", self.namespace.len()).unwrap(); |
1496 | 1.74k | return false; |
1497 | | } |
1498 | | }, |
1499 | | |
1500 | | // Not defined? Claim it. |
1501 | 44.6k | Entry::Vacant(v) => { |
1502 | 44.6k | v.insert((DefinitionLevel::File, kind)); |
1503 | 44.6k | } |
1504 | | } |
1505 | 44.8k | true |
1506 | 46.6k | } |
1507 | | |
1508 | 42.3k | fn insert_definition(&mut self, name: &str, kind: DefinitionKind) -> bool { |
1509 | 42.3k | match self.namespace.get(name) { |
1510 | | // This name is already defined, so it can't be inserted. |
1511 | 19.6k | Some((DefinitionLevel::File, _)) => return false, |
1512 | 0 | Some(other) => { |
1513 | 0 | panic!("found duplicate definition when should be package-unique: {other:?}") |
1514 | | } |
1515 | 22.6k | None => {} |
1516 | | } |
1517 | 22.6k | let prev = self |
1518 | 22.6k | .namespace |
1519 | 22.6k | .insert(name.to_string(), (DefinitionLevel::Package, kind)); |
1520 | 22.6k | assert!(prev.is_none()); |
1521 | 22.6k | true |
1522 | 42.3k | } |
1523 | | } |
1524 | | |
1525 | 9.38k | fn gen_version_less_than( |
1526 | 9.38k | u: &mut Unstructured<'_>, |
1527 | 9.38k | existing_version: &Option<Version>, |
1528 | 9.38k | ) -> Result<Version> { |
1529 | | const MAX_VERSION_RANGE: u64 = 10; |
1530 | 9.38k | let (major, minor, patch) = match existing_version { |
1531 | 3.42k | Some(v) => (v.major, v.minor, v.patch), |
1532 | 5.95k | None => (MAX_VERSION_RANGE, MAX_VERSION_RANGE, MAX_VERSION_RANGE), |
1533 | | }; |
1534 | | |
1535 | 9.38k | let new_version = Version { |
1536 | 9.38k | major: u.int_in_range(0..=major)?, |
1537 | 9.38k | minor: u.int_in_range(0..=minor)?, |
1538 | 9.38k | patch: u.int_in_range(0..=patch)?, |
1539 | 9.38k | pre: if (u.arbitrary()? && existing_version.is_none()) |
1540 | 4.68k | || existing_version.as_ref().is_some_and(|x| !x.pre.is_empty()) |
1541 | | { |
1542 | 7.40k | semver::Prerelease::new("alpha.0").unwrap() |
1543 | | } else { |
1544 | 1.97k | semver::Prerelease::EMPTY |
1545 | | }, |
1546 | 9.38k | build: if (u.arbitrary()? && existing_version.is_none()) |
1547 | 4.79k | || existing_version |
1548 | 4.79k | .as_ref() |
1549 | 4.79k | .is_some_and(|x| !x.build.is_empty()) |
1550 | | { |
1551 | 7.14k | semver::BuildMetadata::new("1.2.0").unwrap() |
1552 | | } else { |
1553 | 2.23k | semver::BuildMetadata::EMPTY |
1554 | | }, |
1555 | | }; |
1556 | | |
1557 | 9.38k | if let Some(v) = existing_version { |
1558 | 3.42k | assert!(&new_version <= v, "{} <= {}", &new_version, v); |
1559 | 5.95k | } |
1560 | | |
1561 | 9.38k | Ok(new_version) |
1562 | 9.38k | } |
1563 | | |
1564 | 5.95k | fn gen_version(u: &mut Unstructured<'_>) -> Result<Version> { |
1565 | 5.95k | gen_version_less_than(u, &None) |
1566 | 5.95k | } |