1 //! Operations for the `gc` operations. 2 3 use crate::generators::gc_ops::{ 4 limits::GcOpsLimits, 5 types::{CompositeType, RecGroupId, StructType, TypeId, Types}, 6 }; 7 use mutatis::{Context, Generate, mutators as m}; 8 use serde::{Deserialize, Serialize}; 9 use smallvec::SmallVec; 10 use std::collections::BTreeMap; 11 use wasm_encoder::{ 12 CodeSection, ConstExpr, EntityType, ExportKind, ExportSection, Function, FunctionSection, 13 GlobalSection, ImportSection, Instruction, Module, RefType, TableSection, TableType, 14 TypeSection, ValType, 15 }; 16 17 /// A description of a Wasm module that makes a series of `externref` table 18 /// operations. 19 #[derive(Debug, Default, Serialize, Deserialize)] 20 pub struct GcOps { 21 pub(crate) limits: GcOpsLimits, 22 pub(crate) ops: Vec<GcOp>, 23 pub(crate) types: Types, 24 } 25 26 impl GcOps { 27 /// Serialize this module into a Wasm binary. 28 /// 29 /// The module requires several function imports. See this function's 30 /// implementation for their exact types. 31 /// 32 /// The single export of the module is a function "run" that takes 33 /// `self.num_params` parameters of type `externref`. 34 /// 35 /// The "run" function does not terminate; you should run it with limited 36 /// fuel. It also is not guaranteed to avoid traps: it may access 37 /// out-of-bounds of the table. 38 pub fn to_wasm_binary(&mut self) -> Vec<u8> { 39 self.fixup(); 40 41 let mut module = Module::new(); 42 43 // Encode the types for all functions that we are using. 44 let mut types = TypeSection::new(); 45 46 // 0: "gc" 47 types.ty().function( 48 vec![], 49 // Return a bunch of stuff from `gc` so that we exercise GCing when 50 // there is return pointer space allocated on the stack. This is 51 // especially important because the x64 backend currently 52 // dynamically adjusts the stack pointer for each call that uses 53 // return pointers rather than statically allocating space in the 54 // stack frame. 55 vec![ValType::EXTERNREF, ValType::EXTERNREF, ValType::EXTERNREF], 56 ); 57 58 // 1: "run" 59 let mut params: Vec<ValType> = Vec::with_capacity(self.limits.num_params as usize); 60 for _i in 0..self.limits.num_params { 61 params.push(ValType::EXTERNREF); 62 } 63 let results = vec![]; 64 types.ty().function(params, results); 65 66 // 2: `take_refs` 67 types.ty().function( 68 vec![ValType::EXTERNREF, ValType::EXTERNREF, ValType::EXTERNREF], 69 vec![], 70 ); 71 72 // 3: `make_refs` 73 types.ty().function( 74 vec![], 75 vec![ValType::EXTERNREF, ValType::EXTERNREF, ValType::EXTERNREF], 76 ); 77 78 // 4: `take_struct` 79 types.ty().function( 80 vec![ValType::Ref(RefType { 81 nullable: false, 82 heap_type: wasm_encoder::HeapType::ANY, 83 })], 84 vec![], 85 ); 86 87 let struct_type_base: u32 = types.len(); 88 89 let mut rec_groups: BTreeMap<RecGroupId, Vec<TypeId>> = self 90 .types 91 .rec_groups 92 .iter() 93 .copied() 94 .map(|id| (id, Vec::new())) 95 .collect(); 96 97 for (id, ty) in self.types.type_defs.iter() { 98 rec_groups.entry(ty.rec_group).or_default().push(id.clone()); 99 } 100 101 let encode_ty_id = |ty_id: &TypeId| -> wasm_encoder::SubType { 102 let def = &self.types.type_defs[ty_id]; 103 match &def.composite_type { 104 CompositeType::Struct(StructType {}) => wasm_encoder::SubType { 105 is_final: true, 106 supertype_idx: None, 107 composite_type: wasm_encoder::CompositeType { 108 inner: wasm_encoder::CompositeInnerType::Struct(wasm_encoder::StructType { 109 fields: Box::new([]), 110 }), 111 shared: false, 112 describes: None, 113 descriptor: None, 114 }, 115 }, 116 } 117 }; 118 119 let mut struct_count = 0; 120 121 for type_ids in rec_groups.values() { 122 let members: Vec<wasm_encoder::SubType> = type_ids.iter().map(encode_ty_id).collect(); 123 types.ty().rec(members); 124 struct_count += type_ids.len() as u32; 125 } 126 127 let typed_fn_type_base: u32 = struct_type_base + struct_count; 128 129 for i in 0..struct_count { 130 let concrete = struct_type_base + i; 131 types.ty().function( 132 vec![ValType::Ref(RefType { 133 nullable: false, 134 heap_type: wasm_encoder::HeapType::Concrete(concrete), 135 })], 136 vec![], 137 ); 138 } 139 140 // Import the GC function. 141 let mut imports = ImportSection::new(); 142 imports.import("", "gc", EntityType::Function(0)); 143 imports.import("", "take_refs", EntityType::Function(2)); 144 imports.import("", "make_refs", EntityType::Function(3)); 145 imports.import("", "take_struct", EntityType::Function(4)); 146 147 // For each of our concrete struct types, define a function 148 // import that takes an argument of that concrete type. 149 let typed_first_func_index: u32 = imports.len(); 150 151 for i in 0..struct_count { 152 let ty_idx = typed_fn_type_base + i; 153 let name = format!("take_struct_{}", struct_type_base + i); 154 imports.import("", &name, EntityType::Function(ty_idx)); 155 } 156 157 // Define our table. 158 let mut tables = TableSection::new(); 159 tables.table(TableType { 160 element_type: RefType::EXTERNREF, 161 minimum: u64::from(self.limits.table_size), 162 maximum: None, 163 table64: false, 164 shared: false, 165 }); 166 // Define our globals. 167 let mut globals = GlobalSection::new(); 168 for _ in 0..self.limits.num_globals { 169 globals.global( 170 wasm_encoder::GlobalType { 171 val_type: wasm_encoder::ValType::EXTERNREF, 172 mutable: true, 173 shared: false, 174 }, 175 &ConstExpr::ref_null(wasm_encoder::HeapType::EXTERN), 176 ); 177 } 178 179 // Define the "run" function export. 180 let mut functions = FunctionSection::new(); 181 let mut exports = ExportSection::new(); 182 183 let run_defined_idx = functions.len(); 184 functions.function(1); 185 let run_func_index = imports.len() + run_defined_idx; 186 exports.export("run", ExportKind::Func, run_func_index); 187 188 // Give ourselves one scratch local that we can use in various `GcOp` 189 // implementations. 190 let local_decls: Vec<(u32, ValType)> = vec![(1, ValType::EXTERNREF)]; 191 192 let mut func = Function::new(local_decls); 193 func.instruction(&Instruction::Loop(wasm_encoder::BlockType::Empty)); 194 for op in &self.ops { 195 op.insert( 196 &mut func, 197 self.limits.num_params, 198 struct_type_base, 199 typed_first_func_index, 200 ); 201 } 202 func.instruction(&Instruction::Br(0)); 203 func.instruction(&Instruction::End); 204 func.instruction(&Instruction::End); 205 206 let mut code = CodeSection::new(); 207 code.function(&func); 208 209 module 210 .section(&types) 211 .section(&imports) 212 .section(&functions) 213 .section(&tables) 214 .section(&globals) 215 .section(&exports) 216 .section(&code); 217 218 module.finish() 219 } 220 221 /// Computes the abstract stack depth after executing all operations 222 pub fn abstract_stack_depth(&self, index: usize) -> usize { 223 debug_assert!(index <= self.ops.len()); 224 let mut stack: usize = 0; 225 for op in self.ops.iter().take(index) { 226 let pop = op.operands_len(); 227 let push = op.results_len(); 228 stack = stack.saturating_sub(pop); 229 stack += push; 230 } 231 stack 232 } 233 234 /// Fixes this test case such that it becomes valid. 235 /// 236 /// This is necessary because a random mutation (e.g. removing an op in the 237 /// middle of our sequence) might have made it so that subsequent ops won't 238 /// have their expected operand types on the Wasm stack 239 /// anymore. Furthermore, because we serialize and deserialize test cases, 240 /// and libFuzzer will occasionally mutate those serialized bytes directly, 241 /// rather than use one of our custom mutations, we have no guarantee that 242 /// pre-mutation test cases are even valid! Therefore, we always call this 243 /// method before translating this "AST"-style representation into a raw 244 /// Wasm binary. 245 pub fn fixup(&mut self) { 246 self.limits.fixup(); 247 self.types.fixup(&self.limits); 248 249 let mut new_ops = Vec::with_capacity(self.ops.len()); 250 let mut stack = 0; 251 252 for mut op in self.ops.iter().copied() { 253 if self.limits.max_types == 0 254 && matches!( 255 op, 256 GcOp::StructNew(..) | GcOp::TakeStructCall(..) | GcOp::TakeTypedStructCall(..) 257 ) 258 { 259 continue; 260 } 261 if self.limits.num_params == 0 && matches!(op, GcOp::LocalGet(..) | GcOp::LocalSet(..)) 262 { 263 continue; 264 } 265 if self.limits.num_globals == 0 266 && matches!(op, GcOp::GlobalGet(..) | GcOp::GlobalSet(..)) 267 { 268 continue; 269 } 270 271 op.fixup(&self.limits); 272 273 let mut temp = SmallVec::<[_; 4]>::new(); 274 275 while stack < op.operands_len() { 276 temp.push(GcOp::Null()); 277 stack += 1; 278 } 279 280 temp.push(op); 281 stack = stack - op.operands_len() + op.results_len(); 282 283 new_ops.extend(temp); 284 } 285 286 // Insert drops to balance the final stack state 287 for _ in 0..stack { 288 new_ops.push(GcOp::Drop()); 289 } 290 291 self.ops = new_ops; 292 } 293 294 /// Attempts to remove the last opcode from the sequence. 295 /// 296 /// Returns `true` if an opcode was successfully removed, or `false` if the 297 /// list was already empty. 298 pub fn pop(&mut self) -> bool { 299 self.ops.pop().is_some() 300 } 301 } 302 303 macro_rules! define_gc_ops { 304 ( 305 $( 306 $op:ident $( ( $($limit_var:ident : $limit:expr => $ty:ty),* ) )? : $params:expr => $results:expr , 307 )* 308 ) => { 309 /// The operations for the `gc` operations. 310 #[derive(Copy, Clone, Debug, Serialize, Deserialize)] 311 pub(crate) enum GcOp { 312 $( 313 $op ( $( $($ty),* )? ), 314 )* 315 } 316 317 /// Names of the operations for testing purposes. 318 #[cfg(test)] 319 pub const OP_NAMES: &'static[&'static str] = &[ 320 $( 321 stringify!($op), 322 )* 323 ]; 324 325 impl GcOp { 326 #[cfg(test)] 327 pub fn name(&self) -> &'static str { 328 match self { 329 $( 330 Self::$op (..) => stringify!($op), 331 )* 332 } 333 } 334 335 pub fn operands_len(&self) -> usize { 336 match self { 337 $( 338 Self::$op (..) => $params, 339 )* 340 } 341 } 342 343 pub fn results_len(&self) -> usize { 344 match self { 345 $( 346 Self::$op (..) => $results, 347 )* 348 } 349 } 350 } 351 352 $( 353 #[allow(non_snake_case, reason = "macro-generated code")] 354 fn $op( 355 _ctx: &mut mutatis::Context, 356 _limits: &GcOpsLimits, 357 stack: usize, 358 ) -> mutatis::Result<(GcOp, usize)> { 359 #[allow(unused_comparisons, reason = "macro-generated code")] 360 { 361 debug_assert!(stack >= $params); 362 } 363 364 let op = GcOp::$op( 365 $($({ 366 let limit_fn = $limit as fn(&GcOpsLimits) -> $ty; 367 let limit = (limit_fn)(_limits); 368 debug_assert!(limit > 0); 369 m::range(0..=limit - 1).generate(_ctx)? 370 }),*)? 371 ); 372 let new_stack = stack - $params + $results; 373 Ok((op, new_stack)) 374 } 375 )* 376 377 impl GcOp { 378 fn fixup(&mut self, limits: &GcOpsLimits) { 379 match self { 380 $( 381 Self::$op( $( $( $limit_var ),* )? ) => { 382 $( $( 383 let limit_fn = $limit as fn(&GcOpsLimits) -> $ty; 384 let limit = (limit_fn)(limits); 385 debug_assert!(limit > 0); 386 *$limit_var = *$limit_var % limit; 387 )* )? 388 } 389 )* 390 } 391 } 392 393 pub(crate) fn generate( 394 ctx: &mut mutatis::Context, 395 ops: &GcOps, 396 stack: usize, 397 ) -> mutatis::Result<(GcOp, usize)> { 398 let mut valid_choices: Vec< 399 fn(&mut Context, &GcOpsLimits, usize) -> mutatis::Result<(GcOp, usize)> 400 > = vec![]; 401 $( 402 #[allow(unused_comparisons, reason = "macro-generated code")] 403 if stack >= $params $($( 404 && { 405 let limit_fn = $limit as fn(&GcOpsLimits) -> $ty; 406 let limit = (limit_fn)(&ops.limits); 407 limit > 0 408 } 409 )*)? { 410 valid_choices.push($op); 411 } 412 )* 413 414 let f = *ctx.rng() 415 .choose(&valid_choices) 416 .expect("should always have a valid op choice"); 417 418 (f)(ctx, &ops.limits, stack) 419 } 420 } 421 }; 422 } 423 424 define_gc_ops! { 425 Gc : 0 => 3, 426 427 MakeRefs : 0 => 3, 428 TakeRefs : 3 => 0, 429 430 // Add one to make sure that out of bounds table accesses are possible, but still rare. 431 TableGet(elem_index: |ops| ops.table_size + 1 => u32) : 0 => 1, 432 TableSet(elem_index: |ops| ops.table_size + 1 => u32) : 1 => 0, 433 434 GlobalGet(global_index: |ops| ops.num_globals => u32) : 0 => 1, 435 GlobalSet(global_index: |ops| ops.num_globals => u32) : 1 => 0, 436 437 LocalGet(local_index: |ops| ops.num_params => u32) : 0 => 1, 438 LocalSet(local_index: |ops| ops.num_params => u32) : 1 => 0, 439 440 StructNew(type_index: |ops| ops.max_types => u32) : 0 => 0, 441 TakeStructCall(type_index: |ops| ops.max_types => u32) : 1 => 0, 442 TakeTypedStructCall(type_index: |ops| ops.max_types => u32) : 1 => 0, 443 444 Drop : 1 => 0, 445 446 Null : 0 => 1, 447 } 448 449 impl GcOp { 450 fn insert( 451 self, 452 func: &mut Function, 453 scratch_local: u32, 454 struct_type_base: u32, 455 typed_first_func_index: u32, 456 ) { 457 let gc_func_idx = 0; 458 let take_refs_func_idx = 1; 459 let make_refs_func_idx = 2; 460 let take_structref_idx = 3; 461 462 match self { 463 Self::Gc() => { 464 func.instruction(&Instruction::Call(gc_func_idx)); 465 } 466 Self::MakeRefs() => { 467 func.instruction(&Instruction::Call(make_refs_func_idx)); 468 } 469 Self::TakeRefs() => { 470 func.instruction(&Instruction::Call(take_refs_func_idx)); 471 } 472 Self::TableGet(x) => { 473 func.instruction(&Instruction::I32Const(x.cast_signed())); 474 func.instruction(&Instruction::TableGet(0)); 475 } 476 Self::TableSet(x) => { 477 func.instruction(&Instruction::LocalSet(scratch_local)); 478 func.instruction(&Instruction::I32Const(x.cast_signed())); 479 func.instruction(&Instruction::LocalGet(scratch_local)); 480 func.instruction(&Instruction::TableSet(0)); 481 } 482 Self::GlobalGet(x) => { 483 func.instruction(&Instruction::GlobalGet(x)); 484 } 485 Self::GlobalSet(x) => { 486 func.instruction(&Instruction::GlobalSet(x)); 487 } 488 Self::LocalGet(x) => { 489 func.instruction(&Instruction::LocalGet(x)); 490 } 491 Self::LocalSet(x) => { 492 func.instruction(&Instruction::LocalSet(x)); 493 } 494 Self::Drop() => { 495 func.instruction(&Instruction::Drop); 496 } 497 Self::Null() => { 498 func.instruction(&Instruction::RefNull(wasm_encoder::HeapType::EXTERN)); 499 } 500 Self::StructNew(x) => { 501 func.instruction(&Instruction::StructNew(x + struct_type_base)); 502 func.instruction(&Instruction::Call(take_structref_idx)); 503 } 504 Self::TakeStructCall(x) => { 505 func.instruction(&Instruction::StructNew(x + struct_type_base)); 506 func.instruction(&Instruction::Call(take_structref_idx)); 507 } 508 Self::TakeTypedStructCall(x) => { 509 let s = struct_type_base + x; 510 let f = typed_first_func_index + x; 511 func.instruction(&Instruction::StructNew(s)); 512 func.instruction(&Instruction::Call(f)); 513 } 514 } 515 } 516 } 517