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; 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; 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 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 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 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 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 loads = new ArrayList<>(); for (int i = 0; i < numLoads; i++) loads.add(i); HashSet 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 slowResult = new ArrayList<>(); for (String tag : locales) if (!slowResult.contains(tag)) slowResult.add(tag); LinkedHashSet 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 loads = new ArrayList<>(); for (int i = 0; i < n; i++) loads.add(i); HashSet 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"); } }