mirror of
https://github.com/storytold/deckcraft.git
synced 2026-11-06 02:19:31 -06:00
Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b3cebb96b6 |
@@ -171,9 +171,7 @@ impl Dictionary {
|
||||
["COMPOUNDWORDMAX", n, ..] => d.compound_max = n.parse().unwrap_or(0),
|
||||
["REP", n] => {
|
||||
for _ in 0..n.parse::<usize>().unwrap_or(0) {
|
||||
// The count is the file's claim: stop when the records run out.
|
||||
let Some(line) = lines.next() else { break };
|
||||
let r: Vec<&str> = line.split_whitespace().collect();
|
||||
let r: Vec<&str> = lines.next().unwrap_or("").split_whitespace().collect();
|
||||
if let [_, a, b, ..] = r.as_slice() {
|
||||
d.rep.push((a.replace('_', " "), b.replace('_', " ")));
|
||||
}
|
||||
@@ -184,8 +182,7 @@ impl Dictionary {
|
||||
let Some(flag) = one_flag(Some(flag), mode) else { continue };
|
||||
let cross = cross.eq_ignore_ascii_case("y");
|
||||
for _ in 0..n.parse::<usize>().unwrap_or(0) {
|
||||
let Some(line) = lines.next() else { break };
|
||||
let r: Vec<&str> = line.split_whitespace().collect();
|
||||
let r: Vec<&str> = lines.next().unwrap_or("").split_whitespace().collect();
|
||||
let [_, _, strip, add, rest @ ..] = r.as_slice() else { continue };
|
||||
let (add, cont) = match add.split_once('/') {
|
||||
Some((a, c)) => (a, parse_flags(c, mode)),
|
||||
@@ -579,15 +576,6 @@ fn cache() -> &'static Cache {
|
||||
CACHE.get_or_init(Default::default)
|
||||
}
|
||||
|
||||
/// Language strings remembered by [`dictionary`]; a string past it is resolved again on each call.
|
||||
const MAX_CACHED_LANGS: usize = 256;
|
||||
|
||||
/// Parsed dictionaries by their affix file, so aliases of one language share it.
|
||||
fn by_files() -> &'static Mutex<HashMap<PathBuf, Arc<Dictionary>>> {
|
||||
static M: OnceLock<Mutex<HashMap<PathBuf, Arc<Dictionary>>>> = OnceLock::new();
|
||||
M.get_or_init(Default::default)
|
||||
}
|
||||
|
||||
/// The dictionary for `lang` (a BCP-47 tag such as `en-US`), loaded once per process.
|
||||
pub fn dictionary(lang: &str) -> Option<Arc<Dictionary>> {
|
||||
let cache = cache();
|
||||
@@ -598,23 +586,10 @@ pub fn dictionary(lang: &str) -> Option<Arc<Dictionary>> {
|
||||
return d.clone();
|
||||
}
|
||||
let loaded = find_in(&search_dirs(), lang).and_then(|(a, d)| {
|
||||
// Tags that resolve to the same files share one parsed dictionary.
|
||||
if let Ok(m) = by_files().lock()
|
||||
&& let Some(dict) = m.get(&a)
|
||||
{
|
||||
return Some(dict.clone());
|
||||
}
|
||||
let (bytes_a, bytes_d) = (std::fs::read(&a).ok()?, std::fs::read(d).ok()?);
|
||||
let dict = Arc::new(Dictionary::parse(&bytes_a, &bytes_d));
|
||||
// A concurrent loader of the same files may have inserted first: share its copy.
|
||||
if let Ok(mut m) = by_files().lock() {
|
||||
return Some(m.entry(a).or_insert(dict).clone());
|
||||
}
|
||||
Some(dict)
|
||||
let (a, d) = (std::fs::read(a).ok()?, std::fs::read(d).ok()?);
|
||||
Some(Arc::new(Dictionary::parse(&a, &d)))
|
||||
});
|
||||
if let Ok(mut c) = cache.lock()
|
||||
&& c.len() < MAX_CACHED_LANGS
|
||||
{
|
||||
if let Ok(mut c) = cache.lock() {
|
||||
c.insert(key, loaded.clone());
|
||||
}
|
||||
loaded
|
||||
@@ -905,14 +880,6 @@ SFX D Y 1\nSFX D 0 ed/S .\n";
|
||||
assert!(d.suggest("workingslides", 5).contains(&"working slides".to_string()));
|
||||
}
|
||||
|
||||
/// An affix header can claim billions of records; parsing stops where the file does.
|
||||
#[test]
|
||||
fn a_header_count_past_the_records_ends_with_them() {
|
||||
let aff = b"SFX S Y 4000000000\nSFX S 0 s .\nREP 4000000000\nREP a b\n";
|
||||
let d = Dictionary::parse(aff, b"1\ncat/S\n".as_slice());
|
||||
assert!(d.check("cats"));
|
||||
}
|
||||
|
||||
/// #1175: compound splitting is quadratic in the word's length, so a word longer than
|
||||
/// `MAX_CHECK_CHARS` is not checked; shorter ones still are.
|
||||
#[test]
|
||||
|
||||
@@ -415,6 +415,11 @@ impl CaptureScanner {
|
||||
}
|
||||
|
||||
fn read_parts(src: &(impl ByteSource + ?Sized), parts: &[(u64, u32)]) -> Result<Vec<u8>> {
|
||||
// Ranges come from a scan of some source; check them against this one before sizing a buffer.
|
||||
let len = src.len();
|
||||
if parts.iter().any(|&(o, n)| o.checked_add(u64::from(n)).is_none_or(|end| end > len)) {
|
||||
return Err(Error::Invalid("packet range lies outside the source".into()));
|
||||
}
|
||||
let mut v = Vec::with_capacity(parts.iter().map(|p| p.1 as usize).sum());
|
||||
for &(o, n) in parts {
|
||||
let a = v.len();
|
||||
|
||||
@@ -274,3 +274,13 @@ fn a_budget_too_small_to_search_is_spent() {
|
||||
let f = opus_file(6, 312, 0);
|
||||
assert_eq!(scanner.find(&f, 0, f.len() as u64).unwrap(), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn read_packet_checks_its_ranges_against_the_source_before_allocating() {
|
||||
let f = opus_file(10, 312, 500);
|
||||
let o = open(&f).unwrap();
|
||||
let s = o.stream_of(Codec::Opus).unwrap();
|
||||
// The last packet lies past the end of a source cut short: an error, not a read of missing bytes.
|
||||
let short = &f[..f.len() / 2];
|
||||
assert!(matches!(o.read_packet(&short, s, 9), Err(Error::Invalid(_))));
|
||||
}
|
||||
|
||||
+7
-1
@@ -18,7 +18,13 @@ fn walk(dir: &Path, c: &mut Counts) {
|
||||
let p = e.path();
|
||||
let name = e.file_name();
|
||||
let name = name.to_string_lossy();
|
||||
if p.is_dir() {
|
||||
// `file_type` does not follow links, so a directory link is not walked (and aliases are
|
||||
// not counted twice).
|
||||
let Ok(kind) = e.file_type() else { continue };
|
||||
if kind.is_symlink() {
|
||||
continue;
|
||||
}
|
||||
if kind.is_dir() {
|
||||
// Nested fuzz workspaces and build output are not part of the crate.
|
||||
if name != "target" && name != "fuzz" && !name.starts_with('.') {
|
||||
walk(&p, c);
|
||||
|
||||
Reference in New Issue
Block a user