Skip to content

Commit 68dc3fe

Browse files
authored
fix: child slice selectors only selecting first matching index (#499)
- Fixed a bug where the compiler would erronously mark states with a single slice transition as unitary, even though such transitions could match more than one index.
1 parent 15486dd commit 68dc3fe

6 files changed

Lines changed: 99 additions & 9 deletions

File tree

.github/workflows/book.yml

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -65,15 +65,13 @@ jobs:
6565
target/
6666
key: book-cargo-${{ hashFiles('**/Cargo.toml') }}
6767
- name: cargo install mdbook
68-
if: steps.cache-restore-cargo.outputs.cache-hit != 'true'
6968
uses: baptiste0928/cargo-install@94e1849646e5797d0c8b34d8e525124ae9ae1d86 # v3.0.1
7069
with:
7170
# Name of the crate to install
7271
crate: mdbook
7372
env:
7473
CARGO_TARGET_DIR: target/
7574
- name: cargo install mdbook-katex
76-
if: steps.cache-restore-cargo.outputs.cache-hit != 'true'
7775
uses: baptiste0928/cargo-install@94e1849646e5797d0c8b34d8e525124ae9ae1d86 # v3.0.1
7876
with:
7977
# Name of the crate to install

.github/workflows/clusterfuzzlite-cron.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ jobs:
3838
uses: google/clusterfuzzlite/actions/run_fuzzers@884713a6c30a92e5e8544c39945cd7cb630abcd1 # v1
3939
with:
4040
github-token: ${{ secrets.GITHUB_TOKEN }}
41-
fuzz-seconds: 600
41+
fuzz-seconds: 1200
4242
mode: 'prune'
4343
output-sarif: true
4444
storage-repo: https://${{ secrets.CLUSTERFUZZLITE_STORAGE_TOKEN }}@github.com/rsonquery/rsonpath-fuzz-storage.git
@@ -64,7 +64,7 @@ jobs:
6464
uses: google/clusterfuzzlite/actions/run_fuzzers@884713a6c30a92e5e8544c39945cd7cb630abcd1 # v1
6565
with:
6666
github-token: ${{ secrets.GITHUB_TOKEN }}
67-
fuzz-seconds: 600
67+
fuzz-seconds: 1200
6868
mode: 'coverage'
6969
sanitizer: 'coverage'
7070
storage-repo: https://${{ secrets.CLUSTERFUZZLITE_STORAGE_TOKEN }}@github.com/rsonquery/rsonpath-fuzz-storage.git

.github/workflows/rust.yml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -138,7 +138,6 @@ jobs:
138138
target/
139139
key: ${{ matrix.toolchain }}-${{ matrix.target_triple }}-cargo-${{ hashFiles('**/Cargo.toml') }}
140140
- name: cargo install cargo-hack
141-
if: steps.cache-restore-cargo.outputs.cache-hit != 'true'
142141
uses: baptiste0928/cargo-install@94e1849646e5797d0c8b34d8e525124ae9ae1d86 # v3.0.1
143142
with:
144143
# Name of the crate to install

crates/rsonpath-lib/src/automaton.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,15 @@ impl ArrayTransitionLabel {
122122
Self::Slice(s) => s.contains(index),
123123
}
124124
}
125+
126+
fn matches_at_most_once(&self) -> bool {
127+
match self {
128+
Self::Index(_) => true,
129+
Self::Slice(slice) => {
130+
slice.step == JsonUInt::ZERO && slice.end.map_or(false, |end| slice.start.as_u64() + 1 >= end.as_u64())
131+
}
132+
}
133+
}
125134
}
126135

127136
impl From<JsonUInt> for ArrayTransitionLabel {

crates/rsonpath-lib/src/automaton/minimizer.rs

Lines changed: 60 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -188,10 +188,7 @@ impl<'q> Minimizer<'q> {
188188
debug!("{id} is rejecting");
189189
attrs = attrs.rejecting();
190190
}
191-
if array_transitions.len() + member_transitions.len() == 1 && fallback == Self::rejecting_state() {
192-
debug!("{id} is unitary");
193-
attrs = attrs.unitary();
194-
}
191+
195192
if self.accepting.contains(fallback.0)
196193
|| array_transitions
197194
.iter()
@@ -213,6 +210,23 @@ impl<'q> Minimizer<'q> {
213210
attrs = attrs.has_array_transition_to_accepting();
214211
}
215212

213+
// Unitarity check:
214+
// 1. Fallback rejects.
215+
// 2. Only one transition that can match at most one element in a JSON, either:
216+
// a) member transition; or
217+
// b) array transition that matches only one index.
218+
let is_unitary = {
219+
fallback == Self::rejecting_state()
220+
&& ((member_transitions.len() == 1 && array_transitions.is_empty())
221+
|| (array_transitions.len() == 1
222+
&& member_transitions.is_empty()
223+
&& array_transitions[0].label.matches_at_most_once()))
224+
};
225+
if is_unitary {
226+
debug!("{id} is unitary");
227+
attrs = attrs.unitary();
228+
}
229+
216230
attrs.into()
217231
}
218232

@@ -1260,6 +1274,48 @@ mod tests {
12601274
assert_eq!(result, expected);
12611275
}
12621276

1277+
#[test]
1278+
fn direct_slice() {
1279+
// Query = $[1:3]
1280+
let label = SimpleSlice::new(1.into(), Some(3.into()), 1.into());
1281+
1282+
let nfa = NondeterministicAutomaton {
1283+
ordered_states: vec![
1284+
NfaState::Direct(nfa::Transition::Array(label.into())),
1285+
NfaState::Accepting,
1286+
],
1287+
};
1288+
1289+
let mut result = minimize(nfa).unwrap();
1290+
make_canonical(&mut result);
1291+
let expected = Automaton {
1292+
states: vec![
1293+
StateTable {
1294+
array_transitions: smallvec![],
1295+
member_transitions: smallvec![],
1296+
fallback_state: State(0),
1297+
attributes: StateAttributes::REJECTING,
1298+
},
1299+
StateTable {
1300+
array_transitions: smallvec![ArrayTransition::new(ArrayTransitionLabel::Slice(label), State(2)),],
1301+
member_transitions: smallvec![],
1302+
fallback_state: State(0),
1303+
attributes: StateAttributes::TRANSITIONS_TO_ACCEPTING
1304+
| StateAttributes::HAS_ARRAY_TRANSITION
1305+
| StateAttributes::HAS_ARRAY_TRANSITION_TO_ACCEPTING,
1306+
},
1307+
StateTable {
1308+
array_transitions: smallvec![],
1309+
member_transitions: smallvec![],
1310+
fallback_state: State(0),
1311+
attributes: StateAttributes::ACCEPTING,
1312+
},
1313+
],
1314+
};
1315+
1316+
assert_eq!(result, expected);
1317+
}
1318+
12631319
/// DFA creation is unstable - it can result in many different isomorphic automaton structures.
12641320
/// This function relabels the states in a canonical way so that they can be compared for equality.
12651321
fn make_canonical(dfa: &mut Automaton) {

crates/rsonpath-test/documents/toml/lists.toml

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -377,3 +377,31 @@ nodes = ['''[
377377
0
378378
]
379379
]''']
380+
381+
[[queries]]
382+
query = "$[1:3]"
383+
description = "select the second and third elements"
384+
385+
[queries.results]
386+
count = 2
387+
spans = [[31, 33], [39, 278]]
388+
nodes = [
389+
'[]',
390+
'''[
391+
[],
392+
[
393+
[
394+
[],
395+
0
396+
],
397+
[
398+
[],
399+
0
400+
],
401+
[
402+
[],
403+
0
404+
]
405+
]
406+
]'''
407+
]

0 commit comments

Comments
 (0)