max / makenotwork
- Co-Authored-By
- Claude Opus 5 (1M context) <noreply@anthropic.com>
1 file changed,
+29 insertions,
-15 deletions
| @@ -37,7 +37,22 @@ | |||
| 37 | 37 | /// | |
| 38 | 38 | /// A missing directory is fine and returns zero: `fuzz/regressions/` does not | |
| 39 | 39 | /// exist until the first crash does. | |
| 40 | - | fn replay(dir: &Path, check: impl Fn(&str) + Copy + std::panic::RefUnwindSafe) -> usize { | |
| 40 | + | /// | |
| 41 | + | /// **Each input is replayed on its own thread, with a large stack**, for the | |
| 42 | + | /// reason the css fuzz target spawns one too: an input can flatten to a | |
| 43 | + | /// stylesheet far larger and more deeply nested than itself (214 bytes to | |
| 44 | + | /// 219 KB, measured), and the oracle's reparse of that recurses per nesting | |
| 45 | + | /// level. A debug build's frames are several times a release build's, so this | |
| 46 | + | /// suite aborted on a default 2 MiB test-thread stack while the same inputs | |
| 47 | + | /// passed under `--release`. It aborted on both fw13 and astra, and was found | |
| 48 | + | /// by a `cargo mutants` BASELINE rather than by a test run, because a stack | |
| 49 | + | /// overflow kills the process before libtest can print a result line -- so | |
| 50 | + | /// `cargo test | grep "test result"` showed the lib suite passing and said | |
| 51 | + | /// nothing at all about this one. | |
| 52 | + | /// | |
| 53 | + | /// A spawned thread is also the only option: a stack overflow aborts the | |
| 54 | + | /// process and `catch_unwind` cannot see it. | |
| 55 | + | fn replay(dir: &Path, check: impl Fn(&str) + Copy + Send + 'static) -> usize { | |
| 41 | 56 | let Ok(entries) = std::fs::read_dir(dir) else { | |
| 42 | 57 | return 0; | |
| 43 | 58 | }; | |
| @@ -58,15 +73,15 @@ | |||
| 58 | 73 | let Ok(text) = std::str::from_utf8(&bytes) else { | |
| 59 | 74 | continue; | |
| 60 | 75 | }; | |
| 76 | + | let owned = text.to_string(); | |
| 61 | 77 | ||
| 62 | - | // Panics here name the file: a bare oracle panic in a loop over twenty | |
| 78 | + | let handle = std::thread::Builder::new() | |
| 79 | + | .stack_size(256 * 1024 * 1024) | |
| 80 | + | .spawn(move || check(&owned)) | |
| 81 | + | .expect("spawning the replay thread"); | |
| 82 | + | // Naming the file matters: a bare oracle panic in a loop over twenty | |
| 63 | 83 | // inputs says nothing about which one broke. | |
| 64 | - | let outcome = std::panic::catch_unwind(|| check(text)); | |
| 65 | - | assert!( | |
| 66 | - | outcome.is_ok(), | |
| 67 | - | "oracle failed on {}: {text:?}", | |
| 68 | - | path.display() | |
| 69 | - | ); | |
| 84 | + | assert!(handle.join().is_ok(), "oracle failed on {}", path.display()); | |
| 70 | 85 | count += 1; | |
| 71 | 86 | } | |
| 72 | 87 | count | |
| @@ -77,10 +92,11 @@ | |||
| 77 | 92 | /// running both costs nothing and removes the question of which subdirectory a | |
| 78 | 93 | /// new artifact belongs in. | |
| 79 | 94 | fn replay_both(dir: &Path) -> usize { | |
| 80 | - | let policy = policy(); | |
| 81 | - | let html = replay(dir, |s: &str| custom_pages::oracle::check_html(s, &policy)); | |
| 95 | + | let html = replay(dir, |s: &str| { | |
| 96 | + | custom_pages::oracle::check_html(s, &policy()) | |
| 97 | + | }); | |
| 82 | 98 | let css = replay(dir, |s: &str| { | |
| 83 | - | custom_pages::oracle::check_css(s, OWNER_SCOPE, &policy); | |
| 99 | + | custom_pages::oracle::check_css(s, OWNER_SCOPE, &policy()); | |
| 84 | 100 | }); | |
| 85 | 101 | assert_eq!(html, css, "the same directory yielded two different counts"); | |
| 86 | 102 | html | |
| @@ -88,24 +104,22 @@ | |||
| 88 | 104 | ||
| 89 | 105 | #[test] | |
| 90 | 106 | fn html_seeds_satisfy_the_oracle() { | |
| 91 | - | let policy = policy(); | |
| 92 | 107 | let n = replay( | |
| 93 | 108 | Path::new(env!("CARGO_MANIFEST_DIR")) | |
| 94 | 109 | .join("fuzz/seeds/html") | |
| 95 | 110 | .as_path(), | |
| 96 | - | |s: &str| custom_pages::oracle::check_html(s, &policy), | |
| 111 | + | |s: &str| custom_pages::oracle::check_html(s, &policy()), | |
| 97 | 112 | ); | |
| 98 | 113 | assert!(n > 0, "no HTML seeds found; the corpus is the point"); | |
| 99 | 114 | } | |
| 100 | 115 | ||
| 101 | 116 | #[test] | |
| 102 | 117 | fn css_seeds_satisfy_the_oracle() { | |
| 103 | - | let policy = policy(); | |
| 104 | 118 | let n = replay( | |
| 105 | 119 | Path::new(env!("CARGO_MANIFEST_DIR")) | |
| 106 | 120 | .join("fuzz/seeds/css") | |
| 107 | 121 | .as_path(), | |
| 108 | - | |s: &str| custom_pages::oracle::check_css(s, OWNER_SCOPE, &policy), | |
| 122 | + | |s: &str| custom_pages::oracle::check_css(s, OWNER_SCOPE, &policy()), | |
| 109 | 123 | ); | |
| 110 | 124 | assert!(n > 0, "no CSS seeds found; the corpus is the point"); | |
| 111 | 125 | } |