8000 fix(fuzz): count EDN-separated and valid-at joins in evaluator guard by adityamukho · Pull Request #845 · project-minigraf/minigraf · GitHub
[go: up one dir, main page]

Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions docs/TEST_COVERAGE.md
Original file line number Diff line number Diff line change
Expand Up @@ -147,8 +147,10 @@ The #723 ancestor tests also cover empty deltas, the 128-row demand cap, the eig
The evaluator fuzz guard has a separate regression suite in
`fuzz/tests/datalog_eval_guard.rs`, run by the nightly fuzz workflow. It checks
scope isolation, join-variable declarations, selective joins and cross-product
rejection, including the timeout input from #802. These harness-only tests are
not included in the engine test count. Run them without building the engine:
rejection, including the timeout input from #802 and all three artifacts from #835.
The #835 cases cover EDN comma separators, adjacent symbol/keyword tokens,
and the per-visible-fact multiplication of `:db/valid-at` patterns. These
harness-only tests are not included in the engine test count. Run them without building the engine:

```bash
rustc --edition=2021 --test fuzz/tests/datalog_eval_guard.rs -o /tmp/datalog-eval-guard-tests
Expand Down
50 changes: 47 additions & 3 deletions fuzz/fuzz_targets/datalog_eval/guard.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@ pub(crate) const MAX_JOIN_ROWS: u64 = 50_000;
/// independently or once per outer binding: outer constraints must not shrink
/// their estimate (#802). Each group's join is counted against `FACTS`, and the
/// groups multiply, including groups in different scopes. Join-variable declaration
/// vectors are excluded because they are not fact patterns.
/// vectors are excluded because they are not fact patterns. `:db/valid-at`
/// scans are counted as `[entity _ _]`, one row per visible fact (#835).
/// A group with no matches counts as 1, so an empty pattern never hides a cross
/// product that the planner may build before reaching it.
pub(crate) fn join_rows_bound(s: &str) -> u64 {
Expand Down Expand Up @@ -100,11 +101,18 @@ fn patterns(s: &str) -> Vec<(usize, [&str; 3])> {
}
']' => {
if let Some((from, scope)) = start.take() {
let mut tokens = s[from..i].split_whitespace();
let mut tokens = pattern_tokens(&s[from..i]);
if let (Some(e), Some(a), Some(v)) =
(tokens.next(), tokens.next(), tokens.next())
{
out.push((scope, [e, a, v]));
// Valid-at scans every visible fact of the entity; its
// value is query metadata, not the stored fact's value.
let p = if a == ":db/valid-at" {
[e, "_", "_"]
} else {
[e, a, v]
};
out.push((scope, p));
}
}
}
Expand All @@ -114,6 +122,42 @@ fn patterns(s: &str) -> Vec<(usize, [&str; 3])> {
out
}

/// Lex the pattern's atoms without requiring whitespace between a symbol and
/// keyword/string. EDN commas separate tokens, including before a variable
/// (#835). Keep strings whole so their contents cannot become join variables.
fn pattern_tokens(mut s: &str) -> impl Iterator<Item = &str> {
std::iter::from_fn(move || {
s = s.trim_start_matches(|c: char| c.is_whitespace() || c == ',');
if s.is_empty() {
return None;
}
let mut end = s.len();
if s.starts_with('"') {
let mut escaped = false;
for (i, c) in s.char_indices().skip(1) {
if escaped {
escaped = false;
} else if c == '\\' {
escaped = true;
} else if c == '"' {
end = i + 1;
break;
}
}
} else {
for (i, c) in s.char_indices().skip(1) {
if c.is_whitespace() || matches!(c, ',' | ':' | '"') {
end = i;
break;
}
}
}
let (token, rest) = s.split_at(end);
s = rest;
Some(token)
})
}

fn is_wildcard(t: &str) -> bool {
t == "_" || t.starts_with("?_")
}
Expand Down
41 changes: 41 additions & 0 deletions fuzz/tests/datalog_eval_guard.rs
8000
Original file line number Diff line number Diff line change
Expand Up @@ -76,3 +76,44 @@ fn sibling_branches_have_separate_scopes() {
2
);
}

#[test]
fn rejects_issue_835_artifacts() {
for input in [
include_str!("fixtures/835-slow.edn"),
include_str!("fixtures/835-slow-2.edn"),
include_str!("fixtures/835-timeout.edn"),
] {
assert!(join_rows_bound(input) > MAX_JOIN_ROWS);
}
}

#[test]
fn commas_do_not_hide_expanding_joins() {
let input = format!(
"[?e ?a ,?v] {}",
(0..16)
.map(|i| format!("[?e ?a{i} ?v{i}] "))
.collect::<String>()
);
assert!(join_rows_bound(&input) > MAX_JOIN_ROWS);
}

#[test]
fn valid_at_patterns_multiply_visible_facts() {
for pattern in ["[?e :db/valid-at ?t] ", "[?e:db/valid-at ?t] "] {
let input = format!("[?e :age ?v] {}", pattern.repeat(16));
assert!(join_rows_bound(&input) > MAX_JOIN_ROWS);
}
}

#[test]
fn separators_preserve_selective_pattern_counts() {
for input in ["[?e,:name,?v]", "[?e:name ?v]"] {
assert_eq!(join_rows_bound(input), 2);
}
assert_eq!(join_rows_bound(r#"[?e:name"Alice"]"#), 1);
assert_eq!(join_rows_bound("[?e:db/valid-at ?t]"), 3);
assert_eq!(join_rows_bound(r#"[_ _ "Alice, :name ?e"]"#), 1);
assert_eq!(join_rows_bound(r#"[_ _ "Alice\", :name ?e"]"#), 1);
}
1 change: 1 addition & 0 deletions fuzz/tests/fixtures/835-slow-2.edn
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
(query [:find ?n :where [?e ?nmjFqfnEEufn ?a] [?e ?ynsfzun ?c] [?e ?lunufn ?a] [?e ?lynnueFFnnueFEEEEEEEEEEEqfn ?c] [?_?e ?lysnufn ?c] [?e ?lu?ueFEEEEEEEEEEEqfn ?c] [?_?e ?ljFqfn ,?c] [?e ?nynEEEEEEEFEEhEEEEEEEEEEEEqfn ?cfalse] [?_?e ?lysnufn ?c] [?e ?lu?ueFEEEEEEEEEEEqfn ?c] [?_?e ?ljFqfn ?c] [?e ?nynEEEEEEEFEEhEEqfn ?g] [?e ?lynufn ?q] [?e ?lynnueFF-nnyneFFFufn ?s] [?e ?lynnue ?] [?e ?l3fn ?k ] [?d ?lynEEEGEEMEAEOEEufn ?aUUUUUUUUUUUUUUUUUUUUUUU] [?0e ?ynstfun ?h] [?e ?lunufn ?a] [?e ?lynnueFFnnueFEEEEEEEn ?cor-join] [?_?e ?lysnufnc ?] [?m ?l0m ?n] [(>=?n "e")?NNNNNNNNNNNNNNNNNNNNNNNNNNNNNNN?a] [?e ?ynsufun ?c] [?e ?lunufn ?a] [?e ?lynnueFFnnueF1EEEfn ?c] [?e ?lynnueFFnnue666666666nufn ?Sc] [?e ?lynnueFFnnue00000EEqfn ?c] [?_?e ?lysnun ?c] [?_?e ?lysnufn ?c] [?e ?lu?ueFEEEEEEEEEEEqfn ?c] [?_?e ?ljueFEEEEEEEn ?cor-join] [?_?e ?lysnufnc ?] [?m ?l0m ?n] [(>=?n "e")?NNNNNNNNNNNNNNNNNNNNNNN?l?nn?eqfnNNNNNNNN?a] [?e ?ynsufun ?c] [?e ?lunufn ?a] [?_?e ?lysnufnc ?] [?m ?l0m ?n] [(>=?n "e")?NNNNNNNNNNNNNNNNNNNNNNNNNNNNNN] [?e ?lunufn ?a] [?e ?lynnueFFnnueFEEEEEEEEEEEqfn ?c] [?_?e ?lysnufn ?c] [?e ?lu?ueFEEEEEEEEEEEqfn ?c] [?_?e ?ljFqfn ?c] [?e ?nynEEEEEEEFEEhEEqfn ?g] [?e ?lynufn ?q] [?e ?lynnueFF-nnyneFFFufn ?s] [?e ?lynnue ?] [?e ?l3fn ?k ] [?d ? ?aUUUUUUUUUUUUUUUUUUUUUUU] [?0e ?ynsufun ?h] [?e ?lunufn ?a] [?e ?lynnueFFnnueFEEEEEEEn ?cor-join] [?_?e ?lysnufnc ?] [?m ?l0m ?n] [(>=?n "e")?NNNNNNNNNNNNNNNNNNNNNNNNNNNNNNN?a] [?e ?ynsufun ?c] [?e ?lunufn ?a] [?e ?lynnueFFnnueF1EEEfn ?c] [?e ?lynnueFFnnue666666666nufn ?Sc] [?e ?lynnueFFnnue0000000sufun ?c] [?e ?lunufn ?a] [?e ?lynnueFFnnueFEEEEfn ?c] [?e ?lynnueFFnnue666666666nufn ?Sc] [?e ?lynnueFFnnue000000sufun ?c] [?e ?lunufn ?a] [?e ?lynnueFFnnueFEEEEfn ?c] [?e ?lynnueFFnrue666666666nufn ?Sc] [?e ?lynnueFFnnue000000001306772764176710315e ?n] [(>=?n "e")]])
Expand Down
1 change: 1 addition & 0 deletions fuzz/tests/fixtures/835-slow.edn
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
(query [:find ?n :where [?e ?nmjFqfnEEufn ?a] [?e ?ynsfzun ?c] [?e ?lunufn ?a] [?e ?lynnueFFnnueFEEEEEEEEEEEqfn ?c] [?_?e ?lysnufn ?c] [?e ?lu?ueFEEEEEEEEEEEqfn ?c] [?_?e ?ljFqfn ,?c] [?e ?nynEEEEEEEFEEhEEEEEEEEEEEEqfn ?cfalse] [?_?e ?lysnufn ?c] [?e ?lu?ueFEEEEEEEEEEEqfn ?c] [?_?e ?ljFqfn ?c] [?e ?nynEEEEEEEFEEhEEqfn ?g] [?e ?lynufn ?q] [?e ?lynnueFF-nnyneFFFufn ?s] [?e ?lynnue ?] [?e ?l3fn ?k ] [?d ?lynEEEGEEMEAEOEEufn ?aUUUUUUUUUUUUUUUUUUUUUUU] [?0e ?ynstfun ?h] [?e ?lunufn ?a] [?e ?lynnueFFnnueFEEEEEEEn ?cor-join] [?_?e ?lysnufnc ?] [?m ?l0m ?n] [(>=?n "e")?NNNNNNNNNNNNNNNNNNNNNNNNNNNNNNN?a] [?e ?ynsufun ?c] [?e ?lunufn ?a] [?e ?lynnueFFnnueF1EEEfn ?c] [?e ?lynnueFFnnue666666666nufn ?Sc] [?e ?lynnueFFnnue00000EEqfn ?c] [?_?e ?lysnun ?c] [?_?e ?lysnufn ?c] [?e ?lu?ueFEEEEEEEEEEEqfn ?c] [?_?e ?ljueFEEEEEEEn ?cor-join] [?_?e ?lysnufnc ?] [?m ?l0m ?n] [(>=?n "e")?NNNNNNNNNNNNNNNNNNNNNNN?l?nn?eqfnNNNNNNNN?a] [?e ?ynsufun ?c] [?e ?lunufn ?a] [?_?e ?lysnufnc ?] [?m ?l0m ?n] [(>=?n "e")?NNNNNNNNNNNNNNNNNNNNNNNNNNNNNN] [?e ?lunufn ?a] [?e ?lynnueFFnnueFEEEEEEEEEEEqfn ?c] [?_?e ?lysnufn ?c] [?e ?lu?ueFEEEEEEEEEEEqfn ?c] [?_?e ?ljFqfn ?c] [?e ?nynEEEEEEEFEEhEEqfn ?g] [?e ?lynufn ?q] [?e ?lynnueFF-nnyneFFFufn ?s] [?e ?lynnue ?] [?e ?l3fn ?k ] [?d ? ?aUUUUUUUUUUUUUUUUUUUUUUU] [?0e ?ynsufun ?h] [?e ?lunufn ?a] [?e ?lynnueFFnnueFEEEEEEEn ?cor-join] [?_?e ?lysnufnc ?] [?m ?l0m ?n] [(>=?n "e")?NNNNNNNNNNNNNNNNNNNNNNNNNNNNNNN?a] [?e ?ynsufun ?c] [?e ?lunufn ?a] [?e ?lynnueFFnnueF1EEEfn ?c] [?e ?lynnueFFnnue666666666nufn ?Sc] [?e ?lynnueFFnnue0000000sufun ?c] [?e ?lunufn ?a] [?e ?lynnueFFnnueFEEEEfn ?c] [?e ?lynnueFFnnue666666666nufn ?Sc] [?e ?lynnueFFnnue000000sufun ?c] [?e ?lunufn ?a] [?e ?lynnueFFnnueFEEEEfn ?c] [?e ?lynnueFFnnue666666666nufn ?Sc] [?e ?lynnueFFnnue000000001306772764176710315e ?n] [(>=?n "e")]])
Expand Down
15 changes: 15 additions & 0 deletions fuzz/tests/fixtures/835-timeout.edn
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
(query [:find ?n :where [?e ?nmjFqfn ?y] [?e ?nynEEEEEEEEEEEEEEEEqfcn ?] [?_?e ?lysnufn ?c] [?e ?luEEEEEEEEEEEqfn ?c] [?_?e ?lyznufn ?c] [?e ?lunufn ?c] [?e ?lynnu875nufn ?c] [?e ?FFFqfn ?k] [?e ?lynEEEEd?eEEq?lynEEEEd?eEEqfn ?k?g]
[?e ?lynnfnqfn ?y] [?e ?nynEEEEEEEEEEEEEEqfn ?c] [?_?e ?lysnufn ?c] [?e ?luEEEEEEEEEEEqfn ?c] [?_?e ?lyznufn ?c] [?e ?lunufn ?c] [?e ?luEEEEEEEEEEEqfn ?c] [?_?e ?lyznEEEEEEEqfcn ?] [?_?e ?lysnufn ?c] [?e ?luEEEEEEEEEEEqfn ?c] [?_?e ?lyznufn ?c] [?e ?lunufn ?c] [?e ?lynnu875nufn ?c] [?e ?FFFqfn ?k] [?e ?lynEEEEd?eEEq?lynEEEEd?eEEqfn ?k?g]
[?e ?lynnfnqfn ?y] [?e ?nynEEEEEEEEEEEEEEqfn ?c] [?_?e ?lysnufn ?c] [?e ?luEEEEEEEEEEEqfn ?c] [?_?e ?lyznufn ?c] [?e ?lunufn ?c] [?e ?luEEEEEEEEEEEqfn ?c] [?_?e ?lyznufn ?c] [?e ?lunufn ?c] [?e ?lynnu877nufn ?c] [?e ?FFFqfn ?k] [?e ?lynEEEEd?eEEq?lynEEEEdEEqn?fe ?k?g]
[?e ?lynnfn ?g] [?e:db/valid-at ?s] [?e ?g?e?nEfn ?g]
[?e ?lynn ?g] [?e:db/valid-at ?s] [?e ?nEfn ?g]
[?e ?lynnfn ?g] [?e:db/valid-at ?s] [?e ?nEEEEEEq7nufn ?c] [?e ?FFFqfn ?k] [?e ?lynEEEEd?eEEq?lynEEd?eEEqfn ?k5g]
[?e ?lynnfn ?g] [?e:db/valid-at ?s] [?e ?nEfn ?g]
[?e ?lynn ?g] [?e:db/valid-at ?s] [?e ?nEfn ?g]
[?e ?lynnfn ?g] [?e:db/valid-at ?s] [?e ?ufn ?c] [?e ?lunufn ?c] [?e ?lynnu877nufn ?c] [?e ?FFFqfn ?k] [?e ?lynEEEEd?eEEq?lynEEEEdEEqn?fe ?k?g]
[?e ?lynnfn ?g] [?e:db/valid-at ?s] [?e ?g?e?nEfn ?g]
[?e ?lynn ?g] 5618 [?e:db/valid-at ?s] [?e ?nEfn ?g]
[?e ?lynnfn ?g] [?e:db/valid-at ?s] [?e ?nEEEEEEq7nufn ?c] [?e ?FFFqfn ?k] [?e ?lynEEEEd?eEEq?lynEEd?eEEqfn ?k5g]
[?e ?lynnfn ?g] [?e:db/valid-at ?s] [?e ?nEfn ?g]
[?e ?lynn ?g] [?e:db/valid-at ?s] [?e ?nEfn ?g]
[?e ?lynnfn ?g] [?e:db/valid-at ?s] [?e ?nEEEEEEqff ?c] [?_?e ?3e ?n] [(>=?n "e")]])
Expand Down
Loading
0