java-topology/defects/v8/unit/V8Algorithm.java

331 lines
13 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.ArrayList;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List;
/**
* V8Algorithm
*
* Unit tests for two CWE-407 defects in V8:
*
* v8-0002: Intl::CanonicalizeLocaleList — src/objects/intl-objects.cc:940
* seen is a std::vector<std::string>; each locale membership check
* is O(seen.size()) → total O(N²) for N input locales.
* Fix: parallel std::unordered_set for O(1) membership, keep vector
* for ordered output (ECMA-402 spec requires insertion order).
*
* v8-0003: SLPTree::TryReduceLoadChain — src/compiler/revectorizer.cc:538
* loads is ZoneVector<Node*>; inner-loop membership check is
* std::find(loads.begin(), loads.end(), *it) → O(N) per step
* → O(N² × L) total for N loads, L effect-chain length.
* Fix: ZoneUnorderedSet<Node*> built once before the loops → O(L).
*
* No external dependencies. Run with:
* javac -d . V8Algorithm.java && java -ea unit.V8Algorithm
*/
public class V8Algorithm {
// =======================================================================
// v8-0002: CanonicalizeLocaleList deduplication
// =======================================================================
/**
* Defective: ArrayList + linear contains — mirrors std::vector::find on seen.
* Returns the number of string comparisons performed (instrumented).
*/
static long canonicalizeLocaleListSlow(String[] inputLocales) {
ArrayList<String> seen = new ArrayList<>();
long comparisons = 0;
for (String tag : inputLocales) {
// O(seen.size()) linear scan — the defect
boolean found = false;
for (String existing : seen) {
comparisons++;
if (existing.equals(tag)) {
found = true;
break;
}
}
if (!found) {
seen.add(tag);
}
}
return comparisons;
}
/**
* Fixed: insertion-order LinkedHashSet for O(1) contains, preserving spec order.
* Returns the number of hash lookups performed (instrumented as 1 per input).
*/
static long canonicalizeLocaleListFast(String[] inputLocales) {
LinkedHashSet<String> seen = new LinkedHashSet<>();
long lookups = 0;
for (String tag : inputLocales) {
lookups++; // one O(1) hash lookup per input
seen.add(tag); // no-op if already present
}
return lookups;
}
/** Build a locale array: N entries cycling over `distinct` unique tags. */
static String[] makeLocales(int n, int distinct) {
String[] tags = new String[n];
for (int i = 0; i < n; i++) {
tags[i] = "locale-" + (i % distinct);
}
return tags;
}
// =======================================================================
// v8-0003: TryReduceLoadChain membership test
// =======================================================================
/**
* Defective: List.contains (O(N)) inside inner loop — mirrors
* std::find(loads.begin(), loads.end(), *it).
*
* Simulates: for each load (N), walk a chain of L steps; at each step
* check membership with linear scan.
*
* Returns total comparison count.
*/
static long tryReduceLoadChainSlow(int numLoads, int chainLen) {
List<Integer> loads = new ArrayList<>();
for (int i = 0; i < numLoads; i++) loads.add(i);
long comparisons = 0;
for (int loadIdx = 0; loadIdx < numLoads; loadIdx++) {
// Walk the simulated effect chain
for (int step = 0; step < chainLen; step++) {
// Probe value cycles through loads to produce hits
int probe = step % numLoads;
// O(N) linear scan — the defect
for (int j = 0; j < loads.size(); j++) {
comparisons++;
if (loads.get(j).equals(probe)) break;
}
}
}
return comparisons;
}
/**
* Fixed: HashSet built once; O(1) contains in inner loop.
* Returns total lookup count.
*/
static long tryReduceLoadChainFast(int numLoads, int chainLen) {
List<Integer> loads = new ArrayList<>();
for (int i = 0; i < numLoads; i++) loads.add(i);
HashSet<Integer> loadsSet = new HashSet<>(loads); // built once: O(N)
long lookups = 0;
for (int loadIdx = 0; loadIdx < numLoads; loadIdx++) {
for (int step = 0; step < chainLen; step++) {
int probe = step % numLoads;
lookups++; // O(1) hash lookup — the fix
loadsSet.contains(probe);
}
}
return lookups;
}
// =======================================================================
// Tests — v8-0002
// =======================================================================
static void test1_intl_correctness() {
// Both paths must return the same deduplication result.
int n = 20, distinct = 5;
String[] locales = makeLocales(n, distinct);
ArrayList<String> slowResult = new ArrayList<>();
for (String tag : locales) if (!slowResult.contains(tag)) slowResult.add(tag);
LinkedHashSet<String> fastResult = new LinkedHashSet<>();
for (String tag : locales) fastResult.add(tag);
assert slowResult.size() == fastResult.size()
: "result sizes differ: slow=" + slowResult.size() + " fast=" + fastResult.size();
assert slowResult.equals(new ArrayList<>(fastResult))
: "result order differs";
System.out.printf(
"test1 [v8-0002 correctness]: n=%d distinct=%d slow_size=%d fast_size=%d ORDER_PRESERVED%n",
n, distinct, slowResult.size(), fastResult.size());
}
static void test2_intl_ratio_at_n500() {
int n = 500, distinct = 250;
String[] locales = makeLocales(n, distinct);
long slow = canonicalizeLocaleListSlow(locales);
long fast = canonicalizeLocaleListFast(locales);
double ratio = (double) slow / Math.max(1, fast);
System.out.printf(
"test2 [v8-0002 ratio N=500]: slow_comparisons=%d fast_lookups=%d ratio=%.1fx%n",
slow, fast, ratio);
assert ratio >= 5.0
: "expected ratio >= 5x at N=500, got " + ratio;
}
static void test3_intl_all_distinct() {
int n = 500;
String[] locales = makeLocales(n, n); // all unique
long slow = canonicalizeLocaleListSlow(locales);
long fast = canonicalizeLocaleListFast(locales);
// All distinct: slow = 0+1+2+...+(n-1) = n*(n-1)/2
long expectedSlow = (long) n * (n - 1) / 2;
double ratio = (double) slow / Math.max(1, fast);
System.out.printf(
"test3 [v8-0002 all-distinct N=500]: slow=%d (expect=%d) fast=%d ratio=%.1fx%n",
slow, expectedSlow, fast, ratio);
assert slow == expectedSlow
: "slow comparisons=" + slow + " expected=" + expectedSlow;
assert ratio >= 100.0
: "expected ratio >= 100x for all-distinct N=500, got " + ratio;
}
static void test4_intl_scaling() {
// Doubling N (all distinct) should quadruple slow ops (O(N²)), double fast ops (O(N)).
int n1 = 200, n2 = 400;
// All distinct to get clean O(N²) vs O(N) growth
long s1 = canonicalizeLocaleListSlow(makeLocales(n1, n1));
long s2 = canonicalizeLocaleListSlow(makeLocales(n2, n2));
long f1 = canonicalizeLocaleListFast(makeLocales(n1, n1));
long f2 = canonicalizeLocaleListFast(makeLocales(n2, n2));
double slowGrowth = (double) s2 / Math.max(1, s1);
double fastGrowth = (double) f2 / Math.max(1, f1);
System.out.printf(
"test4 [v8-0002 scaling all-distinct]: slow 2x_N growth=%.2fx fast growth=%.2fx%n",
slowGrowth, fastGrowth);
// O(N²): doubling N → ~4x ops; threshold 3x to allow small N effects
assert slowGrowth > 3.0
: "slow should grow ~quadratically on 2x N (all-distinct), got " + slowGrowth;
assert fastGrowth <= 2.5
: "fast should grow at most linearly on 2x N, got " + fastGrowth;
}
// =======================================================================
// Tests — v8-0003
// =======================================================================
static void test5_revec_ratio_n16_chain100() {
int n = 16, chainLen = 100;
long slow = tryReduceLoadChainSlow(n, chainLen);
long fast = tryReduceLoadChainFast(n, chainLen);
double ratio = (double) slow / Math.max(1, fast);
System.out.printf(
"test5 [v8-0003 ratio N=16 L=100]: slow=%d fast=%d ratio=%.1fx%n",
slow, fast, ratio);
assert ratio >= 5.0
: "expected ratio >= 5x at N=16 L=100, got " + ratio;
}
static void test6_revec_ratio_n64_chain50() {
int n = 64, chainLen = 50;
long slow = tryReduceLoadChainSlow(n, chainLen);
long fast = tryReduceLoadChainFast(n, chainLen);
double ratio = (double) slow / Math.max(1, fast);
System.out.printf(
"test6 [v8-0003 ratio N=64 L=50]: slow=%d fast=%d ratio=%.1fx%n",
slow, fast, ratio);
assert ratio >= 20.0
: "expected ratio >= 20x at N=64 L=50, got " + ratio;
}
static void test7_revec_scaling() {
// Doubling N should roughly quadruple slow, double fast.
int chainLen = 50;
int n1 = 32, n2 = 64;
long s1 = tryReduceLoadChainSlow(n1, chainLen);
long s2 = tryReduceLoadChainSlow(n2, chainLen);
long f1 = tryReduceLoadChainFast(n1, chainLen);
long f2 = tryReduceLoadChainFast(n2, chainLen);
double slowGrowth = (double) s2 / Math.max(1, s1);
double fastGrowth = (double) f2 / Math.max(1, f1);
System.out.printf(
"test7 [v8-0003 scaling]: slow 2x_N growth=%.2fx fast growth=%.2fx%n",
slowGrowth, fastGrowth);
assert slowGrowth > 2.5
: "slow should grow super-linearly on 2x N, got " + slowGrowth;
assert fastGrowth <= 3.0
: "fast should grow at most linearly on 2x N, got " + fastGrowth;
}
static void test8_revec_correctness() {
// Slow and fast paths should agree on which probes are in the set.
int n = 20, chainLen = 30;
// Re-implement logic to check that both paths would accept the same elements.
List<Integer> loads = new ArrayList<>();
for (int i = 0; i < n; i++) loads.add(i);
HashSet<Integer> fastSet = new HashSet<>(loads);
long slowHits = 0, fastHits = 0;
for (int step = 0; step < chainLen; step++) {
int probe = step % n;
if (loads.contains(probe)) slowHits++;
if (fastSet.contains(probe)) fastHits++;
}
System.out.printf(
"test8 [v8-0003 correctness]: slow_hits=%d fast_hits=%d%n",
slowHits, fastHits);
assert slowHits == fastHits
: "slow and fast membership results disagree: slow=" + slowHits + " fast=" + fastHits;
}
// =======================================================================
// Main
// =======================================================================
public static void main(String[] args) {
System.out.println("=== V8Algorithm — CWE-407 unit tests ===");
System.out.println(" v8-0002: Intl::CanonicalizeLocaleList (intl-objects.cc:940)");
System.out.println(" v8-0003: SLPTree::TryReduceLoadChain (revectorizer.cc:538)");
System.out.println();
test1_intl_correctness();
System.out.println(" PASS test1_intl_correctness");
test2_intl_ratio_at_n500();
System.out.println(" PASS test2_intl_ratio_at_n500");
test3_intl_all_distinct();
System.out.println(" PASS test3_intl_all_distinct");
test4_intl_scaling();
System.out.println(" PASS test4_intl_scaling");
test5_revec_ratio_n16_chain100();
System.out.println(" PASS test5_revec_ratio_n16_chain100");
test6_revec_ratio_n64_chain50();
System.out.println(" PASS test6_revec_ratio_n64_chain50");
test7_revec_scaling();
System.out.println(" PASS test7_revec_scaling");
test8_revec_correctness();
System.out.println(" PASS test8_revec_correctness");
System.out.println();
System.out.println("8/8 PASS");
}
}