1use anyhow::{Context as _, Result, bail};
8use flex_fuchsia_fuzzer::{self as fuzz, Result_ as FuzzResult};
9use serde_json::Value;
10use sha2::{Digest, Sha256};
11use std::fs;
12use std::path::{Path, PathBuf};
13use url::Url;
14
15pub fn create_dir_at<P: AsRef<Path>, S: AsRef<str>>(parent: P, dirname: S) -> Result<PathBuf> {
17 let mut pathbuf = PathBuf::from(parent.as_ref());
18 pathbuf.push(dirname.as_ref());
19 fs::create_dir_all(&pathbuf)
20 .with_context(|| format!("failed to create directory: '{}'", pathbuf.to_string_lossy()))?;
21 Ok(pathbuf)
22}
23
24pub fn create_artifact_dir<P: AsRef<Path>>(output_dir: P) -> Result<PathBuf> {
26 create_dir_at(output_dir, "artifacts")
27}
28
29pub fn create_corpus_dir<P: AsRef<Path>>(
32 output_dir: P,
33 corpus_type: fuzz::Corpus,
34) -> Result<PathBuf> {
35 match corpus_type {
36 fuzz::Corpus::Seed => create_dir_at(output_dir, "seed-corpus"),
37 fuzz::Corpus::Live => create_dir_at(output_dir, "corpus"),
38 other => unreachable!("unsupported type: {:?}", other),
39 }
40}
41
42pub fn digest_path<P: AsRef<Path>>(out_dir: P, result: Option<FuzzResult>, data: &[u8]) -> PathBuf {
49 let mut path = PathBuf::from(out_dir.as_ref());
50 let prefix = match result {
51 None | Some(FuzzResult::NoErrors) => String::default(),
52 Some(FuzzResult::BadMalloc) => format!("alloc-"),
53 Some(FuzzResult::Crash) => format!("crash-"),
54 Some(FuzzResult::Death) => format!("death-"),
55 Some(FuzzResult::Exit) => format!("exit-"),
56 Some(FuzzResult::Leak) => format!("leak-"),
57 Some(FuzzResult::Oom) => format!("oom-"),
58 Some(FuzzResult::Timeout) => format!("timeout-"),
59 Some(FuzzResult::Cleansed) => format!("cleansed-"),
60 Some(FuzzResult::Minimized) => format!("minimized-"),
61 _ => unreachable!(),
62 };
63 let mut digest = Sha256::new();
64 digest.update(&data);
65 path.push(format!("{}{}", prefix, hex::encode(digest.finalize())));
66 path
67}
68
69pub fn get_fuzzer_urls<P: AsRef<Path>>(tests_json: P) -> Result<Vec<Url>> {
76 let tests_json = tests_json.as_ref();
77 let json_data = fs::read_to_string(tests_json)
78 .context(format!("failed to read '{}'", tests_json.to_string_lossy()))?;
79 parse_tests_json(json_data)
80 .context(format!("failed to parse '{}'", tests_json.to_string_lossy()))
81}
82
83fn parse_tests_json(json_data: String) -> Result<Vec<Url>> {
84 let deserialized = serde_json::from_str(&json_data).context("failed to deserialize")?;
85 let tests = match deserialized {
86 Value::Array(tests) => tests,
87 _ => bail!("root object is not array"),
88 };
89 let mut fuzzer_urls = Vec::new();
90 for test in tests {
91 let metadata = match test.get("test") {
92 Some(Value::Object(metadata)) => metadata,
93 Some(_) => bail!("found 'test' field that is not an object"),
94 None => continue,
95 };
96 let build_rule = match metadata.get("build_rule") {
97 Some(Value::String(build_rule)) => build_rule,
98 Some(_) => bail!("found 'build_rule' field that is not a string"),
99 None => continue,
100 };
101 if build_rule != "fuchsia_fuzzer_package" {
102 continue;
103 }
104 let package_url = match metadata.get("package_url") {
105 Some(Value::String(package_url)) => package_url,
106 Some(_) => bail!("found 'package_url' field that is not a string"),
107 None => continue,
108 };
109 let url = Url::parse(package_url).context("failed to parse URL")?;
110 fuzzer_urls.push(url);
111 }
112 Ok(fuzzer_urls)
113}
114
115#[cfg(test)]
116mod tests {
117 use super::get_fuzzer_urls;
118 use anyhow::Result;
119 use fuchsia_fuzzctl_test::Test;
120 use serde_json::json;
121
122 #[fuchsia::test]
123 async fn test_get_fuzzer_urls() -> Result<()> {
124 let test = Test::try_new()?;
125 let build_dir = test.create_dir("out/default")?;
126 let tests_json = build_dir.join("tests.json");
127
128 let actual = format!("{:?}", get_fuzzer_urls(&tests_json));
130 let expected = format!("failed to read '{}/tests.json'", build_dir.to_string_lossy());
131 assert!(actual.contains(&expected));
132
133 test.write_tests_json(&build_dir, "hello world!\n")?;
135 let actual = format!("{:?}", get_fuzzer_urls(&tests_json));
136 assert!(actual.contains("expected value"));
137
138 let json_data = json!({
140 "foo": 1
141 });
142 test.write_tests_json(&build_dir, json_data.to_string())?;
143 let actual = format!("{:?}", get_fuzzer_urls(&tests_json));
144 assert!(actual.contains("root object is not array"));
145
146 let json_data = json!([]);
148 test.write_tests_json(&build_dir, json_data.to_string())?;
149 let fuzzers = get_fuzzer_urls(&tests_json)?;
150 assert!(fuzzers.is_empty());
151
152 let json_data = json!([
154 {
155 "test": 1
156 }
157 ]);
158 test.write_tests_json(&build_dir, json_data.to_string())?;
159 let actual = format!("{:?}", get_fuzzer_urls(&tests_json));
160 assert!(actual.contains("found 'test' field that is not an object"));
161
162 let json_data = json!([
163 {
164 "test": {
165 "build_rule": 1
166 }
167 }
168 ]);
169 test.write_tests_json(&build_dir, json_data.to_string())?;
170 let actual = format!("{:?}", get_fuzzer_urls(&tests_json));
171 assert!(actual.contains("found 'build_rule' field that is not a string"));
172
173 let json_data = json!([
174 {
175 "test": {
176 "build_rule": "fuchsia_fuzzer_package",
177 "package_url": 1
178 }
179 }
180 ]);
181 test.write_tests_json(&build_dir, json_data.to_string())?;
182 let actual = format!("{:?}", get_fuzzer_urls(&tests_json));
183 assert!(actual.contains("found 'package_url' field that is not a string"));
184
185 let json_data = json!([
186 {
187 "test": {
188 "build_rule": "fuchsia_fuzzer_package",
189 "package_url": "not a valid URL"
190 }
191 }
192 ]);
193 test.write_tests_json(&build_dir, json_data.to_string())?;
194 let actual = format!("{:?}", get_fuzzer_urls(&tests_json));
195 assert!(actual.contains("failed to parse URL"));
196
197 let json_data = json!([
199 {
200 "test": {
201 "name": "host-test"
202 }
203 },
204 {
205 "test": {
206 "build_rule": "fuchsia_fuzzer_package",
207 "package_url": "fuchsia-pkg://fuchsia.com/fake#meta/foo-fuzzer.cm"
208 }
209 },
210 {
211 "test": {
212 "build_rule": "fuchsia_test_package",
213 "package_url": "fuchsia-pkg://fuchsia.com/fake#meta/unittests.cm"
214 }
215 },
216 {
217 "test": {
218 "build_rule": "fuchsia_fuzzer_package",
219 "package_url": "fuchsia-pkg://fuchsia.com/fake#meta/bar-fuzzer.cm"
220 }
221 }
222 ]);
223 test.write_tests_json(&build_dir, json_data.to_string())?;
224 let urls = get_fuzzer_urls(&tests_json)?;
225 assert_eq!(urls[0].as_str(), "fuchsia-pkg://fuchsia.com/fake#meta/foo-fuzzer.cm");
226 assert_eq!(urls[1].as_str(), "fuchsia-pkg://fuchsia.com/fake#meta/bar-fuzzer.cm");
227 Ok(())
228 }
229}