162 lines
5.8 KiB
Java
162 lines
5.8 KiB
Java
package unit;
|
|
|
|
import java.util.ArrayList;
|
|
import java.util.Arrays;
|
|
import java.util.HashSet;
|
|
import java.util.List;
|
|
import java.util.Set;
|
|
|
|
/**
|
|
* CWE-407 unit test: opensearch-001
|
|
* ImmutableCacheStatsHolder.java:232-235 — Arrays.asList(levels).contains() inside
|
|
* a loop over dimensionNames — O(n²) level filtering.
|
|
*
|
|
* Slow path: Arrays.asList().contains() — O(L) per iteration.
|
|
* Fast path: HashSet.contains() — O(1) per iteration.
|
|
*
|
|
* Compile: javac -d . CacheStatsLevelsContains.java
|
|
* Run: java -ea unit.CacheStatsLevelsContains
|
|
*/
|
|
public class CacheStatsLevelsContains {
|
|
|
|
/**
|
|
* Simulates the defective filterLevels().
|
|
* Returns the number of element comparisons performed.
|
|
*/
|
|
static long slowFilterLevels(String[] levels, List<String> dimensionNames) {
|
|
if (levels == null) return 0;
|
|
|
|
List<String> levelsList = Arrays.asList(levels); // O(n) contains() scan
|
|
List<String> result = new ArrayList<>();
|
|
long comparisons = 0;
|
|
|
|
for (String dimensionName : dimensionNames) {
|
|
// charge the linear scan cost
|
|
comparisons += levelsList.size();
|
|
if (levelsList.contains(dimensionName)) {
|
|
result.add(dimensionName);
|
|
}
|
|
}
|
|
return comparisons;
|
|
}
|
|
|
|
/**
|
|
* Simulates the fixed filterLevels() using HashSet.
|
|
* Returns the number of hash lookups performed.
|
|
*/
|
|
static long fastFilterLevels(String[] levels, List<String> dimensionNames) {
|
|
if (levels == null) return 0;
|
|
|
|
Set<String> levelsSet = new HashSet<>(Arrays.asList(levels));
|
|
List<String> result = new ArrayList<>();
|
|
long operations = 0;
|
|
|
|
for (String dimensionName : dimensionNames) {
|
|
operations++; // O(1) hash lookup
|
|
if (levelsSet.contains(dimensionName)) {
|
|
result.add(dimensionName);
|
|
}
|
|
}
|
|
return operations;
|
|
}
|
|
|
|
/** Run both paths and return filtered list (for correctness check). */
|
|
static List<String> filterSlow(String[] levels, List<String> dimensionNames) {
|
|
if (levels == null) return new ArrayList<>(dimensionNames);
|
|
List<String> levelsList = Arrays.asList(levels);
|
|
List<String> result = new ArrayList<>();
|
|
for (String d : dimensionNames) {
|
|
if (levelsList.contains(d)) result.add(d);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
static List<String> filterFast(String[] levels, List<String> dimensionNames) {
|
|
if (levels == null) return new ArrayList<>(dimensionNames);
|
|
Set<String> levelsSet = new HashSet<>(Arrays.asList(levels));
|
|
List<String> result = new ArrayList<>();
|
|
for (String d : dimensionNames) {
|
|
if (levelsSet.contains(d)) result.add(d);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
public static void main(String[] args) {
|
|
int passed = 0;
|
|
int total = 0;
|
|
|
|
// Test 1: correctness — same output from both paths
|
|
{
|
|
total++;
|
|
String[] levels = {"shard", "node"};
|
|
List<String> dims = new ArrayList<>(Arrays.asList("index", "shard", "node", "tier"));
|
|
|
|
List<String> slowResult = filterSlow(levels, dims);
|
|
List<String> fastResult = filterFast(levels, dims);
|
|
|
|
assert slowResult.equals(fastResult) :
|
|
"Results differ: slow=" + slowResult + " fast=" + fastResult;
|
|
assert slowResult.size() == 2 : "Expected 2, got " + slowResult.size();
|
|
System.out.printf("Test 1 (correctness): both = %s%n", slowResult);
|
|
passed++;
|
|
}
|
|
|
|
// Test 2: null levels — pass-through
|
|
{
|
|
total++;
|
|
List<String> dims = new ArrayList<>(Arrays.asList("a", "b", "c"));
|
|
long slowCost = slowFilterLevels(null, dims);
|
|
long fastCost = fastFilterLevels(null, dims);
|
|
assert slowCost == 0 && fastCost == 0;
|
|
System.out.printf("Test 2 (null levels): slow=%d fast=%d%n", slowCost, fastCost);
|
|
passed++;
|
|
}
|
|
|
|
// Test 3: cost ratio — medium scale
|
|
{
|
|
total++;
|
|
int D = 100; // dimension names (like cache dimension keys in a large cluster)
|
|
int L = 50; // levels requested
|
|
|
|
List<String> dimensionNames = new ArrayList<>();
|
|
for (int i = 0; i < D; i++) dimensionNames.add("dim" + i);
|
|
|
|
String[] levels = new String[L];
|
|
for (int i = 0; i < L; i++) levels[i] = "dim" + (i * 2); // every other dim
|
|
|
|
long slowCost = slowFilterLevels(levels, dimensionNames);
|
|
long fastCost = fastFilterLevels(levels, dimensionNames);
|
|
|
|
assert slowCost > fastCost :
|
|
String.format("Expected slow > fast: slow=%d fast=%d", slowCost, fastCost);
|
|
System.out.printf("Test 3 (D=%d L=%d): slow=%d, fast=%d, ratio=%.1fx%n",
|
|
D, L, slowCost, fastCost, (double) slowCost / fastCost);
|
|
passed++;
|
|
}
|
|
|
|
// Test 4: large scale — verify significant speedup
|
|
{
|
|
total++;
|
|
int D = 500;
|
|
int L = 200;
|
|
|
|
List<String> dimensionNames = new ArrayList<>();
|
|
for (int i = 0; i < D; i++) dimensionNames.add("dim" + i);
|
|
|
|
String[] levels = new String[L];
|
|
for (int i = 0; i < L; i++) levels[i] = "dim" + i;
|
|
|
|
long slowCost = slowFilterLevels(levels, dimensionNames);
|
|
long fastCost = fastFilterLevels(levels, dimensionNames);
|
|
|
|
double ratio = (double) slowCost / fastCost;
|
|
assert ratio > 10.0 :
|
|
String.format("Expected >10x speedup at D=%d L=%d, got %.1fx", D, L, ratio);
|
|
System.out.printf("Test 4 (D=%d L=%d): slow=%d, fast=%d, speedup=%.1fx%n",
|
|
D, L, slowCost, fastCost, ratio);
|
|
passed++;
|
|
}
|
|
|
|
System.out.printf("%n%d/%d PASS%n", passed, total);
|
|
}
|
|
}
|