vertx: 1 CWE-407 defect — HAManager nodeLeft List.contains O(N×M) in cluster failover loop
This commit is contained in:
parent
4483a2ee12
commit
555896829a
3 changed files with 249 additions and 0 deletions
|
|
@ -0,0 +1,24 @@
|
|||
# UNDF: UNDF-2026-000000570
|
||||
--- a/vertx-core/src/main/java/io/vertx/core/impl/HAManager.java
|
||||
+++ b/vertx-core/src/main/java/io/vertx/core/impl/HAManager.java
|
||||
@@ -304,15 +304,15 @@ public class HAManager {
|
||||
// We also check for and potentially resume any previous failovers that might have failed
|
||||
// We can determine this if there any ids in the cluster map which aren't in the node list
|
||||
- List<String> nodes = clusterManager.getNodes();
|
||||
+ Set<String> nodes = new HashSet<>(clusterManager.getNodes());
|
||||
|
||||
for (Map.Entry<String, String> entry: clusterMap.entrySet()) {
|
||||
if (!leftNodeID.equals(entry.getKey()) && !nodes.contains(entry.getKey())) {
|
||||
JsonObject haInfo = new JsonObject(entry.getValue());
|
||||
checkFailover(entry.getKey(), haInfo);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void addHaInfoIfLost() {
|
||||
- if (clusterManager.getNodes().contains(nodeID) && !clusterMap.containsKey(nodeID)) {
|
||||
+ if (new HashSet<>(clusterManager.getNodes()).contains(nodeID) && !clusterMap.containsKey(nodeID)) {
|
||||
synchronized (haInfo) {
|
||||
clusterMap.put(nodeID, haInfo.encode());
|
||||
}
|
||||
BIN
defects/vertx/unit/VertxTest.class
Normal file
BIN
defects/vertx/unit/VertxTest.class
Normal file
Binary file not shown.
225
defects/vertx/unit/VertxTest.java
Normal file
225
defects/vertx/unit/VertxTest.java
Normal file
|
|
@ -0,0 +1,225 @@
|
|||
import java.util.*;
|
||||
|
||||
/**
|
||||
* CWE-407 unit tests for Vert.x core defects.
|
||||
*
|
||||
* vertx-0001: HAManager.nodeLeft() uses List<String>.contains() inside a loop
|
||||
* over clusterMap.entrySet() → O(N×M) where N = clusterMap size, M = node count.
|
||||
* Fix: convert List<String> to HashSet<String> before the loop → O(N+M).
|
||||
*
|
||||
* Additionally: HAManager.addHaInfoIfLost() calls
|
||||
* clusterManager.getNodes().contains(nodeID) on every nodeAdded/nodeLeft event
|
||||
* → O(M) per event with List, O(1) with Set.
|
||||
*/
|
||||
public class VertxTest {
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
// vertx-0001 helpers — simulate the HAManager.nodeLeft inner loop
|
||||
// -----------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Defective: List<String>.contains() called inside a for-each over
|
||||
* clusterMapKeys → O(N × M) total.
|
||||
*
|
||||
* Mirrors HAManager lines 307-314:
|
||||
* List<String> nodes = clusterManager.getNodes();
|
||||
* for (Map.Entry<String,String> entry : clusterMap.entrySet()) {
|
||||
* if (!leftNodeID.equals(entry.getKey()) && !nodes.contains(entry.getKey())) {
|
||||
* checkFailover(...)
|
||||
* }
|
||||
* }
|
||||
*/
|
||||
static long nodeLeftDefect(List<String> clusterMapKeys, List<String> nodes, String leftNodeID) {
|
||||
long ops = 0;
|
||||
for (String key : clusterMapKeys) {
|
||||
ops++;
|
||||
if (!leftNodeID.equals(key)) {
|
||||
// List.contains = linear scan, O(M) worst case
|
||||
boolean found = nodes.contains(key);
|
||||
ops += nodes.size(); // account for full scan (worst case)
|
||||
}
|
||||
}
|
||||
return ops;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fixed: wrap nodes in HashSet once, then O(1) membership test per entry.
|
||||
*/
|
||||
static long nodeLeftFixed(List<String> clusterMapKeys, List<String> nodes, String leftNodeID) {
|
||||
Set<String> nodeSet = new HashSet<>(nodes); // O(M) once
|
||||
long ops = nodes.size(); // cost of building the set
|
||||
for (String key : clusterMapKeys) {
|
||||
ops++;
|
||||
if (!leftNodeID.equals(key)) {
|
||||
boolean found = nodeSet.contains(key); // O(1)
|
||||
ops++;
|
||||
}
|
||||
}
|
||||
return ops;
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
// vertx-0001 test A — nodeLeft loop op-count ratio
|
||||
// -----------------------------------------------------------------------
|
||||
|
||||
static void testVertx0001_opCount() {
|
||||
int N = 1000; // clusterMap size
|
||||
int M = 1000; // node count
|
||||
|
||||
List<String> clusterMapKeys = new ArrayList<>(N);
|
||||
List<String> nodes = new ArrayList<>(M);
|
||||
|
||||
for (int i = 0; i < N; i++) clusterMapKeys.add("node-" + i);
|
||||
for (int i = 0; i < M; i++) nodes.add("node-" + i);
|
||||
|
||||
String leftNode = "node-0";
|
||||
|
||||
long defectOps = nodeLeftDefect(clusterMapKeys, nodes, leftNode);
|
||||
long fixedOps = nodeLeftFixed(clusterMapKeys, nodes, leftNode);
|
||||
double ratio = (double) defectOps / fixedOps;
|
||||
|
||||
System.out.printf(
|
||||
"vertx-0001 nodeLeft op-count: defect=%d fixed=%d ratio=%.1fx%n",
|
||||
defectOps, fixedOps, ratio
|
||||
);
|
||||
|
||||
if (ratio < 50.0) {
|
||||
throw new AssertionError(
|
||||
"vertx-0001: expected op-count ratio >= 50x at N=M=1000, got " + ratio);
|
||||
}
|
||||
System.out.println("vertx-0001 op-count: PASS");
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
// vertx-0001 test B — wall-clock timing
|
||||
// -----------------------------------------------------------------------
|
||||
|
||||
static void testVertx0001_timing() {
|
||||
int N = 500;
|
||||
int M = 500;
|
||||
int REPS = 200;
|
||||
|
||||
List<String> clusterMapKeys = new ArrayList<>(N);
|
||||
List<String> nodes = new ArrayList<>(M);
|
||||
for (int i = 0; i < N; i++) clusterMapKeys.add("node-" + i);
|
||||
for (int i = 0; i < M; i++) nodes.add("node-" + i);
|
||||
|
||||
String leftNode = "node-0";
|
||||
|
||||
// Warm up
|
||||
for (int r = 0; r < 10; r++) {
|
||||
simulateNodeLeftDefect(clusterMapKeys, nodes, leftNode);
|
||||
simulateNodeLeftFixed(clusterMapKeys, nodes, leftNode);
|
||||
}
|
||||
|
||||
long t0 = System.nanoTime();
|
||||
for (int r = 0; r < REPS; r++) simulateNodeLeftDefect(clusterMapKeys, nodes, leftNode);
|
||||
long defectNs = System.nanoTime() - t0;
|
||||
|
||||
long t1 = System.nanoTime();
|
||||
for (int r = 0; r < REPS; r++) simulateNodeLeftFixed(clusterMapKeys, nodes, leftNode);
|
||||
long fixedNs = System.nanoTime() - t1;
|
||||
|
||||
double ratio = (double) defectNs / fixedNs;
|
||||
System.out.printf(
|
||||
"vertx-0001 nodeLeft timing: defect=%.2fms fixed=%.2fms ratio=%.1fx%n",
|
||||
defectNs / 1e6, fixedNs / 1e6, ratio
|
||||
);
|
||||
|
||||
if (ratio < 5.0) {
|
||||
throw new AssertionError(
|
||||
"vertx-0001: expected wall-clock ratio >= 5x at N=M=500, got " + ratio);
|
||||
}
|
||||
System.out.println("vertx-0001 timing: PASS");
|
||||
}
|
||||
|
||||
// Real simulation (no op counting) for timing test
|
||||
static int simulateNodeLeftDefect(List<String> clusterMapKeys, List<String> nodes, String leftNodeID) {
|
||||
int failovers = 0;
|
||||
for (String key : clusterMapKeys) {
|
||||
if (!leftNodeID.equals(key) && !nodes.contains(key)) {
|
||||
failovers++;
|
||||
}
|
||||
}
|
||||
return failovers;
|
||||
}
|
||||
|
||||
static int simulateNodeLeftFixed(List<String> clusterMapKeys, List<String> nodes, String leftNodeID) {
|
||||
Set<String> nodeSet = new HashSet<>(nodes);
|
||||
int failovers = 0;
|
||||
for (String key : clusterMapKeys) {
|
||||
if (!leftNodeID.equals(key) && !nodeSet.contains(key)) {
|
||||
failovers++;
|
||||
}
|
||||
}
|
||||
return failovers;
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
// vertx-0001 test C — correctness
|
||||
// -----------------------------------------------------------------------
|
||||
|
||||
static void testVertx0001_correctness() {
|
||||
List<String> nodes = Arrays.asList("alpha", "beta", "gamma", "delta");
|
||||
|
||||
// HashSet must agree with List for all membership queries
|
||||
Set<String> nodeSet = new HashSet<>(nodes);
|
||||
for (String n : nodes) {
|
||||
if (nodes.contains(n) != nodeSet.contains(n)) {
|
||||
throw new AssertionError("Membership mismatch for: " + n);
|
||||
}
|
||||
}
|
||||
if (nodes.contains("omega") != nodeSet.contains("omega")) {
|
||||
throw new AssertionError("Membership mismatch for omega");
|
||||
}
|
||||
|
||||
// nodeLeft logic must produce identical failover sets
|
||||
List<String> clusterMapKeys = Arrays.asList("alpha", "beta", "gamma", "delta", "epsilon");
|
||||
String leftNode = "alpha";
|
||||
|
||||
// nodes = {alpha, beta, gamma, delta} clusterMap has "epsilon" extra
|
||||
List<String> defectiveFailovers = new ArrayList<>();
|
||||
for (String key : clusterMapKeys) {
|
||||
if (!leftNode.equals(key) && !nodes.contains(key)) {
|
||||
defectiveFailovers.add(key);
|
||||
}
|
||||
}
|
||||
|
||||
Set<String> fixedNodeSet = new HashSet<>(nodes);
|
||||
List<String> fixedFailovers = new ArrayList<>();
|
||||
for (String key : clusterMapKeys) {
|
||||
if (!leftNode.equals(key) && !fixedNodeSet.contains(key)) {
|
||||
fixedFailovers.add(key);
|
||||
}
|
||||
}
|
||||
|
||||
if (!defectiveFailovers.equals(fixedFailovers)) {
|
||||
throw new AssertionError(
|
||||
"Failover lists differ: defect=" + defectiveFailovers + " fixed=" + fixedFailovers);
|
||||
}
|
||||
if (fixedFailovers.size() != 1 || !fixedFailovers.get(0).equals("epsilon")) {
|
||||
throw new AssertionError("Expected [epsilon], got: " + fixedFailovers);
|
||||
}
|
||||
|
||||
// addHaInfoIfLost pattern: single .contains() call on list vs set
|
||||
String targetNodeId = "delta";
|
||||
boolean listResult = nodes.contains(targetNodeId);
|
||||
boolean setResult = nodeSet.contains(targetNodeId);
|
||||
if (listResult != setResult) {
|
||||
throw new AssertionError("addHaInfoIfLost: list/set mismatch for " + targetNodeId);
|
||||
}
|
||||
|
||||
System.out.println("vertx-0001 correctness: PASS");
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
// Main
|
||||
// -----------------------------------------------------------------------
|
||||
|
||||
public static void main(String[] args) {
|
||||
testVertx0001_correctness();
|
||||
testVertx0001_opCount();
|
||||
testVertx0001_timing();
|
||||
System.out.println("ALL PASS");
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue