java-topology/defects/selenium/patch/selenium-0001-grid-session-mutator-list-contains.patch
russell@unturf.com 9a0253e724 browser-automation: 4 CWE-407 patches (selenium x2, playwright, webdriverio)
selenium-0001: SessionCapabilitiesMutator list.contains O(NxM) -> LinkedHashSet
  O(N+M). Grid Node session mutation hot path. Bench: 192x at N=M=1000.

selenium-0002: ChromiumOptions merge helpers consolidate four list.contains
  loops behind addArgumentsUnique/addEncodedExtensionsUnique. Bench: 254x
  at N=M=1000.

playwright-0001: roleUtils validRoles / allowsNameFromContent Array.includes
  on 20-70 element constant arrays per element. Converted to Set<string>
  at module load. Bench: 11x at N=10000 elements.

webdriverio-0001: xpath-conditions extractOrConditions orMatches.find +
  values.includes per regex match -> Map<attr, Set<values>>. Bench: 6x at
  K=V=60 in the 'mobileSelectorPerformanceOptimizer'.

Each defect ships: ticket, patch with complexity-gate header, Python
benchmark + correctness test, Makefile, outreach brief. All 16 tests
pass. UNDF IDs: 1276 (playwright), 1277 (selenium-0001), 1288
(selenium-0002), 1289 (webdriverio).
2026-04-22 18:14:39 -04:00

92 lines
3.8 KiB
Diff

# UNDF: UNDF-2026-000001277
# UNDF: UNDF-2026-XXXXXXXXX
# CWE-407: Algorithmic Complexity -- O(N*M) -> O(N+M) in SessionCapabilitiesMutator
#
# Defect: Grid Node session mutator deduplicates args/extensions from client caps
# against slot stereotype caps via ArrayList.contains inside forEach. That
# runs O(M) per iteration across N items. Chromium and Firefox merge paths
# both carry this pattern; Chromium path also applies it to extensions.
#
# Fix: Pre-build a LinkedHashSet from the existing stereotype list once, then
# iterate the incoming list with set.add() which returns true only on first
# insertion. LinkedHashSet preserves insertion order, preserving the original
# semantics. Cost drops to O(N+M) per merged list.
#
# Complexity gate (tests/test-selenium-cwe407.py):
# k-scaling 5x: time ratio must be <17.5x (O(k) ~=5x, not O(k^2) ~=25x)
# N=M=20 per session, merge: must complete in <1ms
--- a/java/src/org/openqa/selenium/grid/node/config/SessionCapabilitiesMutator.java
+++ b/java/src/org/openqa/selenium/grid/node/config/SessionCapabilitiesMutator.java
@@ -19,10 +19,12 @@ package org.openqa.selenium.grid.node.config;
import java.util.ArrayList;
import java.util.HashMap;
+import java.util.LinkedHashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Objects;
+import java.util.Set;
import java.util.function.Function;
import org.openqa.selenium.Capabilities;
import org.openqa.selenium.ImmutableCapabilities;
@@ -133,12 +135,15 @@ public class SessionCapabilitiesMutator implements Function<Capabilities, Capabi
new ArrayList<>(
(List<String>) (stereotypeOptions.getOrDefault(("args"), new ArrayList<>())));
- arguments.forEach(
- arg -> {
- if (!stereotypeArguments.contains(arg)) {
- stereotypeArguments.add(arg);
- }
- });
+ // Pre-build a Set from the existing list for O(1) dedup (was O(M) per iteration,
+ // totaling O(N*M) across the forEach). LinkedHashSet preserves insertion order
+ // so downstream consumers see the same args sequence as the prior implementation.
+ Set<String> seenArgs = new LinkedHashSet<>(stereotypeArguments);
+ for (String arg : arguments) {
+ if (seenArgs.add(arg)) {
+ stereotypeArguments.add(arg);
+ }
+ }
toReturn.put("args", stereotypeArguments);
}
@@ -151,12 +156,12 @@ public class SessionCapabilitiesMutator implements Function<Capabilities, Capabi
new ArrayList<>(
(List<String>) (stereotypeOptions.getOrDefault(("extensions"), new ArrayList<>())));
- extensionList.forEach(
- extension -> {
- if (!stereotypeExtensions.contains(extension)) {
- stereotypeExtensions.add(extension);
- }
- });
+ Set<String> seenExtensions = new LinkedHashSet<>(stereotypeExtensions);
+ for (String extension : extensionList) {
+ if (seenExtensions.add(extension)) {
+ stereotypeExtensions.add(extension);
+ }
+ }
toReturn.put("extensions", stereotypeExtensions);
}
@@ -191,12 +196,12 @@ public class SessionCapabilitiesMutator implements Function<Capabilities, Capabi
new ArrayList<>(
(List<String>) (stereotypeOptions.getOrDefault(("args"), new ArrayList<>())));
- arguments.forEach(
- arg -> {
- if (!stereotypeArguments.contains(arg)) {
- stereotypeArguments.add(arg);
- }
- });
+ Set<String> seenArgs = new LinkedHashSet<>(stereotypeArguments);
+ for (String arg : arguments) {
+ if (seenArgs.add(arg)) {
+ stereotypeArguments.add(arg);
+ }
+ }
toReturn.put("args", stereotypeArguments);
}