1 /* Copyright 2017 Mozilla Foundation 2 * 3 * Licensed under the Apache License, Version 2.0 (the "License"); 4 * you may not use this file except in compliance with the License. 5 * You may obtain a copy of the License at 6 * 7 * http://www.apache.org/licenses/LICENSE-2.0 8 * 9 * Unless required by applicable law or agreed to in writing, software 10 * distributed under the License is distributed on an "AS IS" BASIS, 11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 * See the License for the specific language governing permissions and 13 * limitations under the License. 14 */ 15 16 #[cfg(feature = "std")] 17 #[cfg(test)] 18 mod simple_tests { 19 use crate::operators_validator::OperatorValidatorConfig; 20 use crate::parser::{Parser, ParserInput, ParserState, WasmDecoder}; 21 use crate::primitives::{Operator, SectionCode}; 22 use crate::validator::{ValidatingParser, ValidatingParserConfig}; 23 use std::fs::{read_dir, File}; 24 use std::io::prelude::*; 25 use std::path::PathBuf; 26 27 const VALIDATOR_CONFIG: Option<ValidatingParserConfig> = Some(ValidatingParserConfig { 28 operator_config: OperatorValidatorConfig { 29 enable_threads: true, 30 enable_reference_types: true, 31 enable_simd: true, 32 enable_bulk_memory: true, 33 enable_multi_value: true, 34 35 #[cfg(feature = "deterministic")] 36 deterministic_only: true, 37 }, 38 }); 39 read_file_data(path: &PathBuf) -> Vec<u8>40 fn read_file_data(path: &PathBuf) -> Vec<u8> { 41 println!("Parsing {:?}", path); 42 let mut data = Vec::new(); 43 let mut f = File::open(path).ok().unwrap(); 44 f.read_to_end(&mut data).unwrap(); 45 data 46 } 47 48 #[allow(dead_code)] scan_tests_files(prefix: &str) -> Vec<PathBuf>49 fn scan_tests_files(prefix: &str) -> Vec<PathBuf> { 50 let mut files = Vec::new(); 51 52 for entry in read_dir("tests/").unwrap() { 53 let dir = entry.unwrap(); 54 if !dir.file_type().unwrap().is_file() { 55 continue; 56 } 57 58 let path = dir.path(); 59 let file = path.to_str().unwrap(); 60 61 if file.starts_with(prefix) { 62 files.push(path); 63 } 64 } 65 66 return files; 67 } 68 69 #[test] it_works()70 fn it_works() { 71 for entry in read_dir("tests").unwrap() { 72 let dir = entry.unwrap(); 73 if !dir.file_type().unwrap().is_file() { 74 continue; 75 } 76 let data = read_file_data(&dir.path()); 77 let mut parser = Parser::new(data.as_slice()); 78 let mut max_iteration = 100000000; 79 loop { 80 let state = parser.read(); 81 match *state { 82 ParserState::EndWasm => break, 83 ParserState::Error(err) => panic!("Error: {:?}", err), 84 _ => (), 85 } 86 max_iteration -= 1; 87 if max_iteration == 0 { 88 panic!("Max iterations exceeded"); 89 } 90 } 91 } 92 } 93 94 #[test] validator_not_fails()95 fn validator_not_fails() { 96 for entry in read_dir("tests").unwrap() { 97 let dir = entry.unwrap(); 98 if !dir.file_type().unwrap().is_file() { 99 continue; 100 } 101 let data = read_file_data(&dir.path()); 102 let mut parser = ValidatingParser::new(data.as_slice(), VALIDATOR_CONFIG); 103 let mut max_iteration = 100000000; 104 loop { 105 let state = parser.read(); 106 match *state { 107 ParserState::EndWasm => break, 108 ParserState::Error(err) => panic!("Error: {:?}", err), 109 _ => (), 110 } 111 max_iteration -= 1; 112 if max_iteration == 0 { 113 panic!("Max iterations exceeded"); 114 } 115 } 116 } 117 } 118 119 #[test] validator_fails()120 fn validator_fails() { 121 for entry in read_dir("tests/invalid").unwrap() { 122 let dir = entry.unwrap(); 123 if !dir.file_type().unwrap().is_file() { 124 continue; 125 } 126 let data = read_file_data(&dir.path()); 127 let mut parser = ValidatingParser::new(data.as_slice(), VALIDATOR_CONFIG); 128 let mut max_iteration = 100000000; 129 let mut error = false; 130 loop { 131 let state = parser.read(); 132 if let ParserState::Error(_) = *state { 133 error = true; 134 break; 135 } 136 if let ParserState::EndWasm = *state { 137 break; 138 } 139 max_iteration -= 1; 140 if max_iteration == 0 { 141 panic!("Max iterations exceeded"); 142 } 143 } 144 if !error { 145 panic!("fail is expected"); 146 } 147 } 148 } 149 150 macro_rules! expect_state { 151 ($state:expr, $expected:pat) => {{ 152 { 153 let state: &ParserState = $state; 154 match *state { 155 $expected => (), 156 _ => panic!("Unexpected state during testing: {:?}", state), 157 } 158 } 159 }}; 160 } 161 162 #[test] default_read()163 fn default_read() { 164 let data = read_file_data(&PathBuf::from("tests/simple.wasm")); 165 let mut parser = Parser::new(data.as_slice()); 166 167 expect_state!(parser.read(), ParserState::BeginWasm { .. }); 168 expect_state!(parser.read(), ParserState::BeginSection { code: SectionCode::Type, .. }); 169 expect_state!(parser.read(), ParserState::TypeSectionEntry(_)); 170 expect_state!(parser.read(), ParserState::EndSection); 171 expect_state!(parser.read(), ParserState::BeginSection { code: SectionCode::Function, .. }); 172 expect_state!(parser.read(), ParserState::FunctionSectionEntry(_)); 173 expect_state!(parser.read(), ParserState::EndSection); 174 expect_state!(parser.read(), ParserState::BeginSection { code: SectionCode::Code, .. }); 175 expect_state!(parser.read(), ParserState::BeginFunctionBody { .. }); 176 expect_state!(parser.read(), ParserState::FunctionBodyLocals { .. }); 177 expect_state!(parser.read(), ParserState::CodeOperator(_)); 178 expect_state!(parser.read(), ParserState::CodeOperator(Operator::End)); 179 expect_state!(parser.read(), ParserState::EndFunctionBody); 180 expect_state!(parser.read(), ParserState::EndSection); 181 expect_state!(parser.read(), ParserState::EndWasm); 182 } 183 184 #[test] default_read_with_input()185 fn default_read_with_input() { 186 let data = read_file_data(&PathBuf::from("tests/simple.wasm")); 187 let mut parser = Parser::new(data.as_slice()); 188 189 expect_state!(parser.read(), ParserState::BeginWasm { .. }); 190 expect_state!(parser.read_with_input(ParserInput::Default), 191 ParserState::BeginSection { code: SectionCode::Type, .. }); 192 expect_state!(parser.read(), ParserState::TypeSectionEntry(_)); 193 expect_state!(parser.read(), ParserState::EndSection); 194 expect_state!(parser.read(), ParserState::BeginSection { code: SectionCode::Function, ..}); 195 expect_state!( 196 parser.read_with_input(ParserInput::ReadSectionRawData), 197 ParserState::SectionRawData(_) 198 ); 199 expect_state!(parser.read(), ParserState::EndSection); 200 expect_state!(parser.read(), ParserState::BeginSection { code: SectionCode::Code, .. }); 201 expect_state!(parser.read(), ParserState::BeginFunctionBody { .. }); 202 expect_state!( 203 parser.read_with_input(ParserInput::SkipFunctionBody), 204 ParserState::EndSection 205 ); 206 expect_state!(parser.read(), ParserState::EndWasm); 207 } 208 209 #[test] skipping()210 fn skipping() { 211 let data = read_file_data(&PathBuf::from("tests/naming.wasm")); 212 let mut parser = Parser::new(data.as_slice()); 213 214 expect_state!(parser.read(), 215 ParserState::BeginWasm { .. }); 216 expect_state!(parser.read_with_input(ParserInput::Default), 217 ParserState::BeginSection { code: SectionCode::Type, .. }); 218 expect_state!(parser.read_with_input(ParserInput::SkipSection), 219 ParserState::BeginSection { code: SectionCode::Import, ..}); 220 expect_state!(parser.read_with_input(ParserInput::SkipSection), 221 ParserState::BeginSection { code: SectionCode::Function, ..}); 222 expect_state!(parser.read_with_input(ParserInput::SkipSection), 223 ParserState::BeginSection { code: SectionCode::Global, ..}); 224 expect_state!(parser.read_with_input(ParserInput::SkipSection), 225 ParserState::BeginSection { code: SectionCode::Export, ..}); 226 expect_state!(parser.read_with_input(ParserInput::SkipSection), 227 ParserState::BeginSection { code: SectionCode::Element, ..}); 228 expect_state!(parser.read_with_input(ParserInput::SkipSection), 229 ParserState::BeginSection { code: SectionCode::Code, .. }); 230 expect_state!(parser.read(), 231 ParserState::BeginFunctionBody { .. }); 232 expect_state!(parser.read_with_input(ParserInput::SkipFunctionBody), 233 ParserState::BeginFunctionBody { .. }); 234 expect_state!(parser.read_with_input(ParserInput::SkipFunctionBody), 235 ParserState::BeginFunctionBody { .. }); 236 expect_state!(parser.read_with_input(ParserInput::SkipFunctionBody), 237 ParserState::BeginFunctionBody { .. }); 238 expect_state!(parser.read_with_input(ParserInput::SkipFunctionBody), 239 ParserState::BeginFunctionBody { .. }); 240 expect_state!(parser.read_with_input(ParserInput::SkipFunctionBody), 241 ParserState::BeginFunctionBody { .. }); 242 expect_state!(parser.read_with_input(ParserInput::SkipFunctionBody), 243 ParserState::BeginFunctionBody { .. }); 244 expect_state!( 245 parser.read_with_input(ParserInput::SkipFunctionBody), 246 ParserState::EndSection 247 ); 248 expect_state!(parser.read(), 249 ParserState::BeginSection { code: SectionCode::Custom { .. }, ..}); 250 expect_state!(parser.read_with_input(ParserInput::SkipSection), 251 ParserState::BeginSection { code: SectionCode::Custom { .. }, .. }); 252 expect_state!( 253 parser.read_with_input(ParserInput::SkipSection), 254 ParserState::EndWasm 255 ); 256 } 257 258 #[cfg(feature = "deterministic")] run_deterministic_enabled_test(path: &PathBuf)259 fn run_deterministic_enabled_test(path: &PathBuf) { 260 let data = read_file_data(path); 261 262 let config = Some(ValidatingParserConfig { 263 operator_config: OperatorValidatorConfig { 264 deterministic_only: false, 265 enable_threads: true, 266 enable_reference_types: true, 267 enable_simd: true, 268 enable_bulk_memory: true, 269 enable_multi_value: true, 270 }, 271 }); 272 273 let mut parser = ValidatingParser::new(data.as_slice(), config); 274 let mut error = false; 275 276 loop { 277 let state = parser.read(); 278 if let ParserState::Error(_) = *state { 279 error = true; 280 break; 281 } 282 if let ParserState::EndWasm = *state { 283 break; 284 } 285 } 286 287 assert!(error); 288 } 289 290 #[cfg(feature = "deterministic")] 291 #[test] deterministic_enabled()292 fn deterministic_enabled() { 293 // `float_exprs.*.wasm` 294 let mut tests_count = 0; 295 for path in scan_tests_files("tests/float_exprs.") { 296 run_deterministic_enabled_test(&path); 297 tests_count += 1; 298 } 299 assert_eq!(96, tests_count); 300 301 // `float_memory.*.wasm` 302 let mut tests_count = 0; 303 for path in scan_tests_files("tests/float_memory.") { 304 run_deterministic_enabled_test(&path); 305 tests_count += 1; 306 } 307 assert_eq!(6, tests_count); 308 309 // `float_misc.*.wasm` 310 let mut tests_count = 0; 311 for path in scan_tests_files("tests/float_misc.") { 312 run_deterministic_enabled_test(&path); 313 tests_count += 1; 314 } 315 assert_eq!(1, tests_count); 316 } 317 318 } 319 320 #[cfg(feature = "std")] 321 #[cfg(test)] 322 mod wast_tests { 323 use crate::operators_validator::OperatorValidatorConfig; 324 use crate::parser::{ParserState, WasmDecoder}; 325 use crate::validator::{ValidatingParser, ValidatingParserConfig}; 326 use crate::BinaryReaderError; 327 use std::fs::{read, read_dir}; 328 use wabt::script::{Command, CommandKind, ModuleBinary, ScriptParser}; 329 use wabt::Features; 330 331 const SPEC_TESTS_PATH: &str = "testsuite"; 332 default_config() -> ValidatingParserConfig333 fn default_config() -> ValidatingParserConfig { 334 ValidatingParserConfig { 335 operator_config: OperatorValidatorConfig { 336 enable_threads: false, 337 enable_reference_types: false, 338 enable_simd: false, 339 enable_bulk_memory: false, 340 enable_multi_value: false, 341 342 #[cfg(feature = "deterministic")] 343 deterministic_only: true, 344 }, 345 } 346 } 347 validate_module( module: ModuleBinary, config: ValidatingParserConfig, ) -> Result<(), BinaryReaderError>348 fn validate_module( 349 module: ModuleBinary, 350 config: ValidatingParserConfig, 351 ) -> Result<(), BinaryReaderError> { 352 let data = &module.into_vec(); 353 let mut parser = ValidatingParser::new(data.as_slice(), Some(config)); 354 let mut max_iteration = 100000000; 355 loop { 356 let state = parser.read(); 357 match *state { 358 ParserState::EndWasm => break, 359 ParserState::Error(err) => return Err(err), 360 _ => (), 361 } 362 max_iteration -= 1; 363 if max_iteration == 0 { 364 panic!("Max iterations exceeded"); 365 } 366 } 367 Ok(()) 368 } 369 to_features(config: &ValidatingParserConfig) -> Features370 fn to_features(config: &ValidatingParserConfig) -> Features { 371 let mut features = Features::new(); 372 if config.operator_config.enable_simd { 373 features.enable_simd(); 374 } 375 if config.operator_config.enable_reference_types { 376 features.enable_reference_types(); 377 } 378 features 379 } 380 run_wabt_scripts<F>( filename: &str, wast: &[u8], config: ValidatingParserConfig, skip_test: F, ) where F: Fn(&str, u64) -> bool,381 fn run_wabt_scripts<F>( 382 filename: &str, 383 wast: &[u8], 384 config: ValidatingParserConfig, 385 skip_test: F, 386 ) where 387 F: Fn(&str, u64) -> bool, 388 { 389 println!("Parsing {:?}", filename); 390 // Check if we need to skip entire wast file test/parsing. 391 if skip_test(filename, /* line = */ 0) { 392 println!("{}: skipping", filename); 393 return; 394 } 395 396 let features = to_features(&config); 397 let mut parser: ScriptParser<f32, f64> = 398 ScriptParser::from_source_and_name_with_features(wast, filename, features) 399 .expect("script parser"); 400 401 while let Some(Command { kind, line }) = parser.next().expect("parser") { 402 if skip_test(filename, line) { 403 println!("{}:{}: skipping", filename, line); 404 continue; 405 } 406 407 match kind { 408 CommandKind::Module { module, .. } 409 | CommandKind::AssertUninstantiable { module, .. } 410 | CommandKind::AssertUnlinkable { module, .. } => { 411 if let Err(err) = validate_module(module, config.clone()) { 412 panic!("{}:{}: invalid module: {}", filename, line, err.message); 413 } 414 } 415 CommandKind::AssertInvalid { module, .. } 416 | CommandKind::AssertMalformed { module, .. } => { 417 // TODO diffentiate between assert_invalid and assert_malformed 418 if let Ok(_) = validate_module(module, config.clone()) { 419 panic!( 420 "{}:{}: invalid module was successfully parsed", 421 filename, line 422 ); 423 } 424 // TODO: Check the assert_invalid or assert_malformed message 425 } 426 CommandKind::Register { .. } 427 | CommandKind::PerformAction(_) 428 | CommandKind::AssertReturn { .. } 429 | CommandKind::AssertTrap { .. } 430 | CommandKind::AssertExhaustion { .. } 431 | CommandKind::AssertReturnCanonicalNan { .. } 432 | CommandKind::AssertReturnArithmeticNan { .. } => (), 433 } 434 } 435 } 436 run_proposal_tests<F>(name: &str, config: ValidatingParserConfig, skip_test: F) where F: Fn(&str, u64) -> bool,437 fn run_proposal_tests<F>(name: &str, config: ValidatingParserConfig, skip_test: F) 438 where 439 F: Fn(&str, u64) -> bool, 440 { 441 let proposal_dir = format!("{}/proposals/{}", SPEC_TESTS_PATH, name); 442 for entry in read_dir(&proposal_dir).unwrap() { 443 let dir = entry.unwrap(); 444 if !dir.file_type().unwrap().is_file() 445 || dir.path().extension().map(|s| s.to_str().unwrap()) != Some("wast") 446 { 447 continue; 448 } 449 450 let data = read(&dir.path()).expect("wast data"); 451 run_wabt_scripts( 452 dir.file_name().to_str().expect("name"), 453 &data, 454 config, 455 |name, line| skip_test(name, line), 456 ); 457 } 458 } 459 460 #[test] run_proposals_tests()461 fn run_proposals_tests() { 462 run_proposal_tests( 463 "simd", 464 { 465 let mut config: ValidatingParserConfig = default_config(); 466 config.operator_config.enable_simd = true; 467 config 468 }, 469 |name, line| match (name, line) { 470 ("simd_address.wast", _) 471 | ("simd_const.wast", _) 472 | ("simd_f32x4_cmp.wast", _) 473 | ("simd_store.wast", _) 474 | ("simd_lane.wast", _) 475 | ("simd_load.wast", _) => true, 476 _ => false, 477 }, 478 ); 479 480 run_proposal_tests( 481 "reference-types", 482 { 483 let mut config: ValidatingParserConfig = default_config(); 484 config.operator_config.enable_reference_types = true; 485 config 486 }, 487 |name, line| match (name, line) { 488 ("ref_null.wast", _) 489 | ("ref_is_null.wast", _) 490 | ("ref_func.wast", _) 491 | ("linking.wast", _) 492 | ("globals.wast", _) 493 | ("imports.wast", _) 494 | ("br_table.wast", _) 495 | ("select.wast", _) 496 | ("table_get.wast", _) 497 | ("table_set.wast", _) 498 | ("table_size.wast", _) 499 | ("table_fill.wast", _) 500 | ("table_grow.wast", _) 501 | ("exports.wast", _) => true, 502 _ => false, 503 }, 504 ); 505 } 506 507 #[test] run_spec_tests()508 fn run_spec_tests() { 509 for entry in read_dir(SPEC_TESTS_PATH).unwrap() { 510 let dir = entry.unwrap(); 511 if !dir.file_type().unwrap().is_file() 512 || dir.path().extension().map(|s| s.to_str().unwrap()) != Some("wast") 513 { 514 continue; 515 } 516 517 let data = read(&dir.path()).expect("wast data"); 518 run_wabt_scripts( 519 dir.file_name().to_str().expect("name"), 520 &data, 521 default_config(), 522 |_, _| false, 523 ); 524 } 525 } 526 } 527