thrift-0002: t_cpp_generator::is_struct_storage_not_throwing() vector<t_field*> member deduplication uses std::find O(M²) — fix with unordered_set victoria-metrics-0002: MetricName RemoveTagsOn/RemoveTagsIgnoring hasTag() O(T×I) linear scan inside per-metric loop — fix with map-based tag set pulsar-0007: ModularLoadManagerImpl.reapDeadBrokerPreallocations() takes List<String> aliveBrokers, calls contains() O(B) per broker — fix with HashSet kafka-0010: fix unit test worst-case ordering (shared-topic last for slow path)
185 lines
6 KiB
Java
185 lines
6 KiB
Java
package unit;
|
|
|
|
import java.util.ArrayList;
|
|
import java.util.HashMap;
|
|
import java.util.HashSet;
|
|
import java.util.List;
|
|
import java.util.Set;
|
|
|
|
/**
|
|
* CWE-407 unit test: thrift-0002
|
|
*
|
|
* Models t_cpp_generator::is_struct_storage_not_throwing() member deduplication.
|
|
*
|
|
* DEFECT: When a struct contains nested struct fields, the generator accumulates
|
|
* all transitively-reachable fields in a vector<t_field*> members.
|
|
* For each new field from a nested struct, std::find(members.begin(),
|
|
* members.end(), field) is O(M) where M = current accumulated size.
|
|
* Total: O(M²) for M transitively-reachable members.
|
|
*
|
|
* FIX: Maintain a parallel unordered_set<t_field*> for O(1) membership.
|
|
* Total: O(M).
|
|
*
|
|
* Asserts: slowOps > fastOps * 10 at M=500.
|
|
*/
|
|
public class Thrift0002CppGenStructDedupTest {
|
|
|
|
/**
|
|
* Simulates the defective member deduplication.
|
|
* members grows as nested struct fields are discovered.
|
|
* std::find is O(current members size).
|
|
*/
|
|
static long slow(int numMembers) {
|
|
// Simulate: outer struct has 1 field of a nested struct,
|
|
// which has numMembers unique fields.
|
|
List<Integer> members = new ArrayList<>();
|
|
// Start with some initial members
|
|
members.add(0);
|
|
|
|
long ops = 0;
|
|
// Add numMembers fields from the nested struct, each O(members.size()) to check
|
|
for (int i = 1; i <= numMembers; i++) {
|
|
// std::find(members.begin(), members.end(), i)
|
|
boolean found = false;
|
|
for (Integer m : members) {
|
|
ops++;
|
|
if (m.equals(i)) {
|
|
found = true;
|
|
break;
|
|
}
|
|
}
|
|
if (!found) {
|
|
members.add(i);
|
|
}
|
|
}
|
|
return ops;
|
|
}
|
|
|
|
/**
|
|
* Simulates the patched version.
|
|
* Parallel HashSet for O(1) membership check.
|
|
*/
|
|
static long fast(int numMembers) {
|
|
List<Integer> members = new ArrayList<>();
|
|
Set<Integer> memberSet = new HashSet<>();
|
|
members.add(0);
|
|
memberSet.add(0);
|
|
|
|
long ops = 0;
|
|
for (int i = 1; i <= numMembers; i++) {
|
|
ops++; // O(1) hash probe
|
|
if (!memberSet.contains(i)) {
|
|
members.add(i);
|
|
memberSet.add(i);
|
|
}
|
|
}
|
|
return ops;
|
|
}
|
|
|
|
/**
|
|
* Worst case: later members that are duplicates require scanning the full list.
|
|
* First M unique, then M duplicates appended — all duplicates require full scan.
|
|
*/
|
|
static long slowWorstCase(int numUnique) {
|
|
List<Integer> members = new ArrayList<>();
|
|
for (int i = 0; i < numUnique; i++) members.add(i);
|
|
|
|
long ops = 0;
|
|
// Try to add each member again — all fail (duplicates), scanning full list
|
|
for (int i = 0; i < numUnique; i++) {
|
|
for (Integer m : members) {
|
|
ops++;
|
|
if (m.equals(i)) break; // always found
|
|
}
|
|
}
|
|
return ops;
|
|
}
|
|
|
|
static long fastWorstCase(int numUnique) {
|
|
Set<Integer> memberSet = new HashSet<>();
|
|
for (int i = 0; i < numUnique; i++) memberSet.add(i);
|
|
|
|
long ops = 0;
|
|
for (int i = 0; i < numUnique; i++) {
|
|
ops++; // O(1)
|
|
memberSet.contains(i);
|
|
}
|
|
return ops;
|
|
}
|
|
|
|
public static void main(String[] args) {
|
|
int passed = 0;
|
|
int total = 0;
|
|
|
|
// Test 1: 200 new unique members being deduplicated
|
|
{
|
|
total++;
|
|
int M = 200;
|
|
long sOps = slow(M);
|
|
long fOps = fast(M);
|
|
// slow: sum(1..M) ≈ M²/2 = 20000
|
|
// fast: M = 200
|
|
boolean ok = sOps > fOps * 10L;
|
|
System.out.printf("Test 1 [M=%d slow=%d fast=%d ratio=%.1fx]: %s%n",
|
|
M, sOps, fOps, (double) sOps / fOps, ok ? "PASS" : "FAIL");
|
|
if (ok) passed++;
|
|
}
|
|
|
|
// Test 2: 500 new unique members
|
|
{
|
|
total++;
|
|
int M = 500;
|
|
long sOps = slow(M);
|
|
long fOps = fast(M);
|
|
boolean ok = sOps > fOps * 50L;
|
|
System.out.printf("Test 2 [M=%d slow=%d fast=%d ratio=%.1fx]: %s%n",
|
|
M, sOps, fOps, (double) sOps / fOps, ok ? "PASS" : "FAIL");
|
|
if (ok) passed++;
|
|
}
|
|
|
|
// Test 3: worst case 300 existing members, 300 duplicates attempted
|
|
{
|
|
total++;
|
|
int M = 300;
|
|
long sOps = slowWorstCase(M);
|
|
long fOps = fastWorstCase(M);
|
|
// slow: avg M/2 per duplicate probe = M²/2 = 45000
|
|
// fast: M = 300
|
|
boolean ok = sOps > fOps * 50L;
|
|
System.out.printf("Test 3 worst-case [M=%d slow=%d fast=%d ratio=%.1fx]: %s%n",
|
|
M, sOps, fOps, (double) sOps / fOps, ok ? "PASS" : "FAIL");
|
|
if (ok) passed++;
|
|
}
|
|
|
|
// Test 4: correctness — both produce same deduplicated set
|
|
{
|
|
total++;
|
|
int M = 100;
|
|
List<Integer> slowResult = new ArrayList<>();
|
|
slowResult.add(0);
|
|
for (int i = 1; i <= M; i++) {
|
|
if (!slowResult.contains(i)) slowResult.add(i);
|
|
}
|
|
|
|
List<Integer> fastResult = new ArrayList<>();
|
|
Set<Integer> fastSet = new HashSet<>();
|
|
fastResult.add(0);
|
|
fastSet.add(0);
|
|
for (int i = 1; i <= M; i++) {
|
|
if (!fastSet.contains(i)) {
|
|
fastResult.add(i);
|
|
fastSet.add(i);
|
|
}
|
|
}
|
|
|
|
boolean ok = slowResult.equals(fastResult) && slowResult.size() == M + 1;
|
|
System.out.printf("Test 4 [correctness M=%d slowSize=%d fastSize=%d equal=%b]: %s%n",
|
|
M, slowResult.size(), fastResult.size(), slowResult.equals(fastResult),
|
|
ok ? "PASS" : "FAIL");
|
|
if (ok) passed++;
|
|
}
|
|
|
|
System.out.printf("%d/%d PASS%n", passed, total);
|
|
if (passed != total) System.exit(1);
|
|
}
|
|
}
|