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).
152 lines
5.6 KiB
Diff
152 lines
5.6 KiB
Diff
# UNDF: UNDF-2026-000001288
|
|
# UNDF: UNDF-2026-XXXXXXXXX
|
|
# CWE-407: Algorithmic Complexity -- O(N*M) -> O(N+M) in ChromiumOptions merge paths
|
|
#
|
|
# Defect: ChromiumOptions.mergeInPlace and mergeInOptionsFromCaps both dedup
|
|
# incoming args and extensions against existing lists via List.contains
|
|
# inside forEach. Four separate loops carry the same O(M)-per-iteration
|
|
# pattern, giving O(N*M) per merge call.
|
|
#
|
|
# Fix: Pass through shared helpers addArgumentsUnique(Collection) and
|
|
# addEncodedExtensionsUnique(Collection) that build a HashSet view of the
|
|
# existing list once, then iterate the incoming collection with O(1)
|
|
# lookups. All four call sites call into the helpers.
|
|
#
|
|
# 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=50 merge: must complete in <1ms
|
|
--- a/java/src/org/openqa/selenium/chromium/ChromiumOptions.java
|
|
+++ b/java/src/org/openqa/selenium/chromium/ChromiumOptions.java
|
|
@@ -25,10 +25,12 @@ import java.io.IOException;
|
|
import java.nio.file.Files;
|
|
import java.util.ArrayList;
|
|
import java.util.Base64;
|
|
+import java.util.Collection;
|
|
import java.util.Collections;
|
|
import java.util.HashMap;
|
|
+import java.util.HashSet;
|
|
import java.util.List;
|
|
import java.util.Map;
|
|
import java.util.Set;
|
|
import java.util.TreeMap;
|
|
import java.util.stream.Stream;
|
|
@@ -270,6 +272,30 @@ public class ChromiumOptions<T extends ChromiumOptions<?>>
|
|
}
|
|
}
|
|
|
|
+ /**
|
|
+ * Append each distinct argument in {@code toAdd} to the internal {@code args} list.
|
|
+ * Uses a HashSet view of {@code args} for O(1) membership testing; the prior
|
|
+ * implementation used List.contains inside forEach, giving O(N*M) per merge.
|
|
+ */
|
|
+ private void addArgumentsUnique(Collection<String> toAdd) {
|
|
+ Set<String> seen = new HashSet<>(args);
|
|
+ for (String arg : toAdd) {
|
|
+ if (seen.add(arg)) {
|
|
+ args.add(arg);
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+
|
|
+ /** Same pattern as {@link #addArgumentsUnique} for the string-encoded extensions list. */
|
|
+ private void addEncodedExtensionsUnique(Collection<String> toAdd) {
|
|
+ Set<String> seen = new HashSet<>(extensions);
|
|
+ for (String ext : toAdd) {
|
|
+ if (seen.add(ext)) {
|
|
+ extensions.add(ext);
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+
|
|
protected void mergeInPlace(Capabilities capabilities) {
|
|
Require.nonNull("Capabilities to merge", capabilities);
|
|
|
|
@@ -280,28 +306,21 @@ public class ChromiumOptions<T extends ChromiumOptions<?>>
|
|
|
|
if (name.equals("args") && capabilities.getCapability(name) != null) {
|
|
List<String> arguments = capabilities.required("args");
|
|
- arguments.forEach(
|
|
- arg -> {
|
|
- if (!args.contains(arg)) {
|
|
- addArguments(arg);
|
|
- }
|
|
- });
|
|
+ addArgumentsUnique(arguments);
|
|
}
|
|
|
|
if (name.equals("extensions") && capabilities.getCapability(name) != null) {
|
|
List<Object> extensionList = capabilities.required("extensions");
|
|
- extensionList.forEach(
|
|
- extension -> {
|
|
- if (!extensions.contains(extension)) {
|
|
- if (extension instanceof File) {
|
|
- addExtensions((File) extension);
|
|
- } else if (extension instanceof String) {
|
|
- addEncodedExtensions((String) extension);
|
|
- }
|
|
- }
|
|
- });
|
|
+ // Partition by type, then push each partition through the O(N+M) helpers.
|
|
+ Set<Object> seen = new HashSet<>(extensions);
|
|
+ for (Object extension : extensionList) {
|
|
+ if (seen.add(extension)) {
|
|
+ if (extension instanceof File) {
|
|
+ addExtensions((File) extension);
|
|
+ } else if (extension instanceof String) {
|
|
+ addEncodedExtensions((String) extension);
|
|
+ }
|
|
+ }
|
|
+ }
|
|
}
|
|
|
|
if (name.equals("binary") && capabilities.getCapability(name) != null) {
|
|
@@ -314,14 +333,9 @@ public class ChromiumOptions<T extends ChromiumOptions<?>>
|
|
}
|
|
}
|
|
|
|
if (capabilities instanceof ChromiumOptions) {
|
|
ChromiumOptions<?> options = (ChromiumOptions<?>) capabilities;
|
|
- for (String arg : options.args) {
|
|
- if (!args.contains(arg)) {
|
|
- addArguments(arg);
|
|
- }
|
|
- }
|
|
+ addArgumentsUnique(options.args);
|
|
addExtensions(options.extensionFiles);
|
|
addEncodedExtensions(options.extensions);
|
|
|
|
@@ -343,24 +357,21 @@ public class ChromiumOptions<T extends ChromiumOptions<?>>
|
|
List<Object> extensionList =
|
|
(List<Object>) (options.getOrDefault("extensions", new ArrayList<>()));
|
|
|
|
- arguments.forEach(
|
|
- arg -> {
|
|
- if (!args.contains(arg)) {
|
|
- addArguments(arg);
|
|
- }
|
|
- });
|
|
+ addArgumentsUnique(arguments);
|
|
|
|
- extensionList.forEach(
|
|
- extension -> {
|
|
- if (!extensions.contains(extension)) {
|
|
- if (extension instanceof File) {
|
|
- addExtensions((File) extension);
|
|
- } else if (extension instanceof String) {
|
|
- addEncodedExtensions((String) extension);
|
|
- }
|
|
- }
|
|
- });
|
|
+ Set<Object> seenExtensions = new HashSet<>(extensions);
|
|
+ for (Object extension : extensionList) {
|
|
+ if (seenExtensions.add(extension)) {
|
|
+ if (extension instanceof File) {
|
|
+ addExtensions((File) extension);
|
|
+ } else if (extension instanceof String) {
|
|
+ addEncodedExtensions((String) extension);
|
|
+ }
|
|
+ }
|
|
+ }
|
|
|
|
Object binary = options.get("binary");
|
|
if (binary instanceof String) {
|