java-topology/defects/terraform/unit/TfAwsCloudFormationAlgorithm.java

175 lines
6.2 KiB
Java
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package unit;
import java.util.*;
/**
* TfAwsCloudFormationAlgorithm — CWE-407 benchmark
*
* Models terraform-provider-aws cloudformation/stack_set_instance.go
* findStackInstanceSummariesByFourPartKey():
* SLOW: slices.Contains(orgIDs, v.OrganizationalUnitId) — O(O) per summary
* called S times inside pagination → O(S × O) total
* FAST: pre-build map[string]bool from orgIDs → O(1) per summary
*
* tf-aws-0001
*/
public class TfAwsCloudFormationAlgorithm {
// ------------------------------------------------------------------ nodes
static class Node {
String ouId;
String stackInstanceId;
Node(String ouId, String stackInstanceId) {
this.ouId = ouId;
this.stackInstanceId = stackInstanceId;
}
}
// ------------------------------------------------------------------ result
static class Result {
List<Node> matched;
Result(List<Node> matched) { this.matched = matched; }
}
// ------------------------------------------------------------------ slow (defect)
static class DefectiveFinder {
/**
* For each summary across all pagination pages, scan orgIDs linearly.
* O(S × O) where S = total summaries, O = number of org IDs.
*/
static Result findSummaries(List<List<Node>> pages, List<String> orgIDs) {
List<Node> output = new ArrayList<>();
for (List<Node> page : pages) {
for (Node v : page) {
// CWE-407: linear scan of orgIDs on every summary
if (orgIDs.contains(v.ouId)) { // O(O)
output.add(v);
}
}
}
return new Result(output);
}
}
// ------------------------------------------------------------------ fast (fix)
static class FixedFinder {
/**
* Pre-build a HashSet from orgIDs before the pagination loop.
* O(S + O) total.
*/
static Result findSummaries(List<List<Node>> pages, List<String> orgIDs) {
// CWE-407 fix: O(1) lookup set built once before pagination
Set<String> orgIDSet = new HashSet<>(orgIDs);
List<Node> output = new ArrayList<>();
for (List<Node> page : pages) {
for (Node v : page) {
if (orgIDSet.contains(v.ouId)) { // O(1)
output.add(v);
}
}
}
return new Result(output);
}
}
// ------------------------------------------------------------------ helpers
static List<List<Node>> buildPages(int totalSummaries, int pageSize, int numOUs) {
List<List<Node>> pages = new ArrayList<>();
List<Node> page = new ArrayList<>();
for (int i = 0; i < totalSummaries; i++) {
String ouId = "ou-" + (i % numOUs); // distribute across OUs
page.add(new Node(ouId, "stack-instance-" + i));
if (page.size() == pageSize) {
pages.add(page);
page = new ArrayList<>();
}
}
if (!page.isEmpty()) pages.add(page);
return pages;
}
static List<String> buildOrgIDs(int n) {
List<String> ids = new ArrayList<>(n);
for (int i = 0; i < n; i++) {
ids.add("ou-" + i);
}
return ids;
}
static long benchSlow(int S, int O, int iters) {
List<List<Node>> pages = buildPages(S, 100, O * 2);
List<String> orgIDs = buildOrgIDs(O);
long start = System.nanoTime();
for (int i = 0; i < iters; i++) {
DefectiveFinder.findSummaries(pages, orgIDs);
}
return System.nanoTime() - start;
}
static long benchFast(int S, int O, int iters) {
List<List<Node>> pages = buildPages(S, 100, O * 2);
List<String> orgIDs = buildOrgIDs(O);
long start = System.nanoTime();
for (int i = 0; i < iters; i++) {
FixedFinder.findSummaries(pages, orgIDs);
}
return System.nanoTime() - start;
}
// ------------------------------------------------------------------ main
public static void main(String[] args) {
int passed = 0, total = 0;
// ---- correctness
List<Node> page1 = Arrays.asList(
new Node("ou-1", "s1"), new Node("ou-2", "s2"), new Node("ou-3", "s3")
);
List<Node> page2 = Arrays.asList(
new Node("ou-4", "s4"), new Node("ou-1", "s5")
);
List<List<Node>> pages = Arrays.asList(page1, page2);
List<String> orgIDs = Arrays.asList("ou-1", "ou-3");
Result slowR = DefectiveFinder.findSummaries(pages, orgIDs);
Result fastR = FixedFinder.findSummaries(pages, orgIDs);
assert slowR.matched.size() == 3 : "slow: expected 3 matches, got " + slowR.matched.size();
assert fastR.matched.size() == 3 : "fast: expected 3 matches, got " + fastR.matched.size();
assert slowR.matched.size() == fastR.matched.size() : "slow/fast count mismatch";
System.out.println("Correctness: PASS (slow.matched.size == fast.matched.size == 3)");
// ---- performance
int ITERS = 50;
int[][] scenarios = {{2000, 200}, {4000, 350}, {7000, 500}};
System.out.printf("%-6s %-6s %-12s %-12s %s%n", "S", "O", "slow(ns)", "fast(ns)", "ratio");
for (int[] sc : scenarios) {
int S = sc[0], O = sc[1];
// warm-up (extra iterations to stabilize JIT at larger sizes)
benchSlow(S, O, 10); benchFast(S, O, 10);
long slowNs = benchSlow(S, O, ITERS);
long fastNs = benchFast(S, O, ITERS);
double ratio = (double) slowNs / fastNs;
System.out.printf("%-6d %-6d %-12d %-12d %.2fx%n", S, O, slowNs, fastNs, ratio);
total++;
if (ratio >= 5.0) {
System.out.printf(" PASS (ratio=%.2f >= 5.0)%n", ratio);
passed++;
} else {
System.out.printf(" FAIL (ratio=%.2f < 5.0)%n", ratio);
}
}
System.out.printf("%nTests: %d/%d PASS%n", passed, total);
if (passed < total) System.exit(1);
}
}