fuchsia_fuzzctl/
util.rs
1use anyhow::{bail, Context as _, Result};
8use fidl_fuchsia_fuzzer::{self as fuzz, Result_ as FuzzResult};
9use serde_json::Value;
10use sha2::{Digest, Sha256};
11use std::path::{Path, PathBuf};
12use std::{env, fs};
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!("{}{:x}", prefix, digest.finalize()));
66 path
67}
68
69pub fn get_fuzzer_urls(tests_json: &Option<String>) -> Result<Vec<Url>> {
77 let tests_json = match tests_json {
78 Some(tests_json) => Ok(PathBuf::from(tests_json)),
79 None => test_json_path(None).context("tests.json was not provided and could not be found"),
80 }?;
81 let json_data = fs::read_to_string(&tests_json)
82 .context(format!("failed to read '{}'", tests_json.to_string_lossy()))?;
83 parse_tests_json(json_data)
84 .context(format!("failed to parse '{}'", tests_json.to_string_lossy()))
85}
86
87fn test_json_path(fuchsia_dir: Option<&Path>) -> Result<PathBuf> {
88 let fuchsia_dir = match fuchsia_dir {
89 Some(fuchsia_dir) => Ok(fuchsia_dir.to_string_lossy().to_string()),
90 None => env::var("FUCHSIA_DIR").context("FUCHSIA_DIR is not set"),
91 }?;
92 let mut fx_build_dir = PathBuf::from(&fuchsia_dir);
93 fx_build_dir.push(".fx-build-dir");
94 let mut fx_build_dir = fs::read_to_string(&fx_build_dir)
95 .with_context(|| format!("failed to read '{}'", fx_build_dir.to_string_lossy()))?;
96
97 fx_build_dir.retain(|c| !c.is_whitespace());
98 let mut tests_json = PathBuf::from(&fuchsia_dir);
99 tests_json.push(&fx_build_dir);
100 tests_json.push("tests.json");
101 Ok(tests_json)
102}
103
104fn parse_tests_json(json_data: String) -> Result<Vec<Url>> {
105 let deserialized = serde_json::from_str(&json_data).context("failed to deserialize")?;
106 let tests = match deserialized {
107 Value::Array(tests) => tests,
108 _ => bail!("root object is not array"),
109 };
110 let mut fuzzer_urls = Vec::new();
111 for test in tests {
112 let metadata = match test.get("test") {
113 Some(Value::Object(metadata)) => metadata,
114 Some(_) => bail!("found 'test' field that is not an object"),
115 None => continue,
116 };
117 let build_rule = match metadata.get("build_rule") {
118 Some(Value::String(build_rule)) => build_rule,
119 Some(_) => bail!("found 'build_rule' field that is not a string"),
120 None => continue,
121 };
122 if build_rule != "fuchsia_fuzzer_package" {
123 continue;
124 }
125 let package_url = match metadata.get("package_url") {
126 Some(Value::String(package_url)) => package_url,
127 Some(_) => bail!("found 'package_url' field that is not a string"),
128 None => continue,
129 };
130 let url = Url::parse(package_url).context("failed to parse URL")?;
131 fuzzer_urls.push(url);
132 }
133 Ok(fuzzer_urls)
134}
135
136#[cfg(test)]
137mod tests {
138 use super::{get_fuzzer_urls, test_json_path};
139 use anyhow::Result;
140 use fuchsia_fuzzctl_test::Test;
141 use serde_json::json;
142
143 #[fuchsia::test]
144 async fn test_test_json_path() -> Result<()> {
145 let test = Test::try_new()?;
146 let build_dir = test.create_dir("out/default")?;
147
148 let fuchsia_dir = test.root_dir();
150 let actual = format!("{:?}", test_json_path(Some(&fuchsia_dir)));
151 let expected = format!("failed to read '{}/.fx-build-dir'", fuchsia_dir.to_string_lossy());
152 assert!(actual.contains(&expected));
153
154 test.write_fx_build_dir(&build_dir)?;
156 test_json_path(Some(&fuchsia_dir))?;
157 Ok(())
158 }
159
160 #[fuchsia::test]
161 async fn test_get_fuzzer_urls() -> Result<()> {
162 let test = Test::try_new()?;
163 let build_dir = test.create_dir("out/default")?;
164 test.write_fx_build_dir(&build_dir)?;
165 let fuchsia_dir = test.root_dir();
166 let tests_json = test_json_path(Some(fuchsia_dir))?;
167 let tests_json = Some(tests_json.to_string_lossy().to_string());
168
169 let actual = format!("{:?}", get_fuzzer_urls(&tests_json));
171 let expected = format!("failed to read '{}/tests.json'", build_dir.to_string_lossy());
172 assert!(actual.contains(&expected));
173
174 test.write_tests_json(&build_dir, "hello world!\n")?;
176 let actual = format!("{:?}", get_fuzzer_urls(&tests_json));
177 assert!(actual.contains("expected value"));
178
179 let json_data = json!({
181 "foo": 1
182 });
183 test.write_tests_json(&build_dir, json_data.to_string())?;
184 let actual = format!("{:?}", get_fuzzer_urls(&tests_json));
185 assert!(actual.contains("root object is not array"));
186
187 let json_data = json!([]);
189 test.write_tests_json(&build_dir, json_data.to_string())?;
190 let fuzzers = get_fuzzer_urls(&tests_json)?;
191 assert!(fuzzers.is_empty());
192
193 let json_data = json!([
195 {
196 "test": 1
197 }
198 ]);
199 test.write_tests_json(&build_dir, json_data.to_string())?;
200 let actual = format!("{:?}", get_fuzzer_urls(&tests_json));
201 assert!(actual.contains("found 'test' field that is not an object"));
202
203 let json_data = json!([
204 {
205 "test": {
206 "build_rule": 1
207 }
208 }
209 ]);
210 test.write_tests_json(&build_dir, json_data.to_string())?;
211 let actual = format!("{:?}", get_fuzzer_urls(&tests_json));
212 assert!(actual.contains("found 'build_rule' field that is not a string"));
213
214 let json_data = json!([
215 {
216 "test": {
217 "build_rule": "fuchsia_fuzzer_package",
218 "package_url": 1
219 }
220 }
221 ]);
222 test.write_tests_json(&build_dir, json_data.to_string())?;
223 let actual = format!("{:?}", get_fuzzer_urls(&tests_json));
224 assert!(actual.contains("found 'package_url' field that is not a string"));
225
226 let json_data = json!([
227 {
228 "test": {
229 "build_rule": "fuchsia_fuzzer_package",
230 "package_url": "not a valid URL"
231 }
232 }
233 ]);
234 test.write_tests_json(&build_dir, json_data.to_string())?;
235 let actual = format!("{:?}", get_fuzzer_urls(&tests_json));
236 assert!(actual.contains("failed to parse URL"));
237
238 let json_data = json!([
240 {
241 "test": {
242 "name": "host-test"
243 }
244 },
245 {
246 "test": {
247 "build_rule": "fuchsia_fuzzer_package",
248 "package_url": "fuchsia-pkg://fuchsia.com/fake#meta/foo-fuzzer.cm"
249 }
250 },
251 {
252 "test": {
253 "build_rule": "fuchsia_test_package",
254 "package_url": "fuchsia-pkg://fuchsia.com/fake#meta/unittests.cm"
255 }
256 },
257 {
258 "test": {
259 "build_rule": "fuchsia_fuzzer_package",
260 "package_url": "fuchsia-pkg://fuchsia.com/fake#meta/bar-fuzzer.cm"
261 }
262 }
263 ]);
264 test.write_tests_json(&build_dir, json_data.to_string())?;
265 let urls = get_fuzzer_urls(&tests_json)?;
266 assert_eq!(urls[0].as_str(), "fuchsia-pkg://fuchsia.com/fake#meta/foo-fuzzer.cm");
267 assert_eq!(urls[1].as_str(), "fuchsia-pkg://fuchsia.com/fake#meta/bar-fuzzer.cm");
268 Ok(())
269 }
270}