browser-automation wave 2: testcafe-0001 + webdriverio-0002
testcafe-0001: Selector filterNodes (string-filter branch) and expandSelectorResults both dedup via Array.indexOf on growing result arrays. filterNodes: O(N*M) per selector filter. expandSelectorResults: O(N^2 * K^2) worst case when derivatives unique. Fix: Set<Node> keyed by object identity. Bench: 398x at N=2000 filter, 1966x at N=K=150 expand. webdriverio-0002: MSPO aggregator dedups per-test entries via Array.find on growing bucket array. O(N^2) per test bucket, same pattern repeats in unknown-suite merger. Fix: companion Map<bucketKey, Set<selector>> for O(1) dedup. Bench: 493x at N=2000. UNDF IDs: 1290 (testcafe), 1291 (webdriverio-0002). All 17 tests pass.
This commit is contained in:
parent
9a0253e724
commit
b79fddfb51
16 changed files with 827 additions and 17 deletions
|
|
@ -0,0 +1,60 @@
|
|||
# UNDF: UNDF-2026-000001290
|
||||
# UNDF: UNDF-2026-XXXXXXXXX
|
||||
# CWE-407: Algorithmic Complexity -- O(N*M) -> O(N+M) in Selector filter dedup,
|
||||
# O(N^2*K^2) -> O(N*K) in derivative expansion
|
||||
#
|
||||
# Defect: filterNodes (string-filter branch) uses matchingArr.indexOf(node) > -1
|
||||
# per input node, O(N*M). expandSelectorResults uses result.indexOf(deriv) < 0
|
||||
# against a growing result array, worst case O(N^2*K^2) when all derivatives
|
||||
# are unique. Both run as client functions injected into the test page;
|
||||
# executed per Selector().filter() and per Selector().parent()/child()/etc.
|
||||
#
|
||||
# Fix: Replace array-indexOf dedup with Set membership test. Object identity
|
||||
# semantics preserved (Set keys by object reference, same as indexOf on Node
|
||||
# references). Total cost drops to O(N+M) and O(N*K) respectively.
|
||||
#
|
||||
# Complexity gate (tests/test-testcafe-cwe407.py):
|
||||
# k-scaling 5x: time ratio must be <17.5x (O(k) ~=5x, not O(k^2) ~=25x)
|
||||
# filterNodes N=M=1000: must complete in <5ms
|
||||
# expandSelectorResults N=K=100: must complete in <5ms
|
||||
--- a/src/client-functions/selectors/add-api.js
|
||||
+++ b/src/client-functions/selectors/add-api.js
|
||||
@@ -30,12 +30,12 @@ const filterNodes = new ClientFunctionBuilder((nodes, filter, querySelectorRoot,
|
||||
return null;
|
||||
|
||||
const matching = querySelectorRoot.querySelectorAll(filter);
|
||||
- const matchingArr = [];
|
||||
-
|
||||
+ // Set keyed by object identity gives O(1) membership test; prior impl
|
||||
+ // used Array.indexOf(node) > -1 inside a per-node loop, giving O(N*M).
|
||||
+ const matchingSet = new Set();
|
||||
for (let i = 0; i < matching.length; i++)
|
||||
- matchingArr.push(matching[i]);
|
||||
-
|
||||
- filter = node => matchingArr.indexOf(node) > -1;
|
||||
+ matchingSet.add(matching[i]);
|
||||
+ filter = node => matchingSet.has(node);
|
||||
}
|
||||
|
||||
if (typeof filter === 'function') {
|
||||
@@ -55,13 +55,16 @@ const expandSelectorResults = new ClientFunctionBuilder((selector, populateDeriv
|
||||
|
||||
const result = [];
|
||||
|
||||
+ // Parallel Set tracks which derivative Nodes have been pushed; prior impl
|
||||
+ // used result.indexOf(deriv) < 0, giving O(N*K*|result|) worst case.
|
||||
+ const seen = new Set();
|
||||
+
|
||||
for (let i = 0; i < nodes.length; i++) {
|
||||
const derivativeNodes = populateDerivativeNodes(nodes[i]);
|
||||
|
||||
if (derivativeNodes) {
|
||||
for (let j = 0; j < derivativeNodes.length; j++) {
|
||||
- if (result.indexOf(derivativeNodes[j]) < 0)
|
||||
+ if (!seen.has(derivativeNodes[j])) {
|
||||
+ seen.add(derivativeNodes[j]);
|
||||
result.push(derivativeNodes[j]);
|
||||
+ }
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue