331 lines
13 KiB
Java
331 lines
13 KiB
Java
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");
|
||
}
|
||
}
|