package unit; import java.util.*; /** * SolcTest — CWE-407 benchmark for solc-0001 * * solc-0001: TypeChecker::cleanOverloadedDeclarations * File: libsolidity/analysis/TypeChecker.cpp, ~line 3619-3651 * * Real code: * for (Declaration const* declaration: _candidates) * { * ... * if (uniqueDeclarations.end() == find_if( * uniqueDeclarations.begin(), uniqueDeclarations.end(), * [&](Declaration const* d) { * FunctionType const* newFunctionType = d->functionType(false); * if (!newFunctionType) newFunctionType = d->functionType(true); * return newFunctionType && functionType->hasEqualParameterTypes(*newFunctionType); * } * )) * uniqueDeclarations.push_back(declaration); * } * * For N declarations each with a unique signature the inner find_if scans * 0 + 1 + 2 + ... + (N-1) = N*(N-1)/2 entries → O(N²) comparisons. * * Fix: build a seenSignatures unordered_set keyed by the canonical parameter-type * string. Each probe/insert is O(1) → total O(N). * * solc-0002: ContractLevelChecker::findDuplicateDefinitions — CLEAN (CWE-407) * The set::count call is O(log N), making the loop O(N log N) — fine * for small N (< 20 overloads per name). A separate logic defect exists * (loop exits early when i is in reported), but that is not a CWE-407 issue. * No performance patch warranted. */ public class SolcTest { // ----------------------------------------------------------------------- // solc-0001 simulation // ----------------------------------------------------------------------- /** * Simulates the defective cleanOverloadedDeclarations. * * Each "declaration" is represented by its signature string. * uniqueDeclarations accumulates strings already accepted. * For each new candidate we scan uniqueDeclarations linearly to check * whether an equal signature already exists — mirroring the find_if loop. * * @param signatures ordered list of candidate signatures (may contain dupes) * @return number of comparisons performed (the ops count) */ static long slowCleanOverloads(List signatures) { List uniqueDeclarations = new ArrayList<>(); long ops = 0; for (String sig : signatures) { boolean found = false; // O(|uniqueDeclarations|) linear scan — mirrors std::find_if for (String existing : uniqueDeclarations) { ops++; if (existing.equals(sig)) { found = true; break; } } if (!found) { uniqueDeclarations.add(sig); } } return ops; } /** * Simulates the fixed cleanOverloadedDeclarations. * * Uses an unordered_set (HashSet) for O(1) duplicate detection. * Each candidate signature is probed / inserted in O(1). * * @param signatures ordered list of candidate signatures * @return number of ops charged (1 per candidate = O(N)) */ static long fastCleanOverloads(List signatures) { Set seenSignatures = new HashSet<>(); List uniqueDeclarations = new ArrayList<>(); long ops = 0; for (String sig : signatures) { ops++; // O(1) hash probe + insert if (seenSignatures.add(sig)) { uniqueDeclarations.add(sig); } } return ops; } // ----------------------------------------------------------------------- // Helpers // ----------------------------------------------------------------------- /** Build a list of N distinct signatures (all unique — worst case for find_if). */ static List distinctSignatures(int n) { List sigs = new ArrayList<>(n); for (int i = 0; i < n; i++) { // Simulate paramater-type strings like "uint256,address|bool," sigs.add("param" + i + "_type:uint256_" + i + "|ret:bool,"); } return sigs; } /** Build a list of N signatures where half are duplicates of the first half. */ static List halfDupSignatures(int n) { List sigs = new ArrayList<>(n); for (int i = 0; i < n / 2; i++) sigs.add("sig_" + i); for (int i = 0; i < n / 2; i++) sigs.add("sig_" + i); // duplicates return sigs; } static void bench(String label, List sigs) { // warm-up slowCleanOverloads(sigs); fastCleanOverloads(sigs); long t0 = System.nanoTime(); long sOps = slowCleanOverloads(sigs); long slowNs = System.nanoTime() - t0; long t1 = System.nanoTime(); long fOps = fastCleanOverloads(sigs); long fastNs = System.nanoTime() - t1; double ratio = (double) sOps / Math.max(fOps, 1); System.out.printf(" %-60s slow:%6dns (%,6d ops) fast:%6dns (%,6d ops) ratio:%.1fx%n", label, slowNs, sOps, fastNs, fOps, ratio); } // ----------------------------------------------------------------------- // main // ----------------------------------------------------------------------- public static void main(String[] args) { System.out.println("SolcTest — solc-0001: TypeChecker::cleanOverloadedDeclarations O(N²) → O(N)"); System.out.println(); System.out.println(" Benchmark: all-distinct signatures (worst case for find_if)"); int[] sizes = {50, 100, 200, 500}; for (int n : sizes) { bench(String.format("N=%d distinct overload candidates", n), distinctSignatures(n)); } System.out.println(); System.out.println(" Benchmark: half-duplicate signatures"); for (int n : sizes) { bench(String.format("N=%d half-dup overload candidates", n), halfDupSignatures(n)); } System.out.println(); // ---- Assertions ---- int pass = 0; int total = 0; // Test 1: N=100 distinct — slow must be ~N*(N-1)/2 comparisons, fast = N { total++; int n = 100; List sigs = distinctSignatures(n); long sOps = slowCleanOverloads(sigs); long fOps = fastCleanOverloads(sigs); long expectedSlow = (long) n * (n - 1) / 2; // 4950 assert sOps == expectedSlow : "solc-0001 slow ops mismatch: expected " + expectedSlow + " got " + sOps; assert fOps == n : "solc-0001 fast ops mismatch: expected " + n + " got " + fOps; double ratio = (double) sOps / fOps; assert ratio > 10.0 : "solc-0001 expected >10x ratio at N=100; got " + ratio; System.out.printf(" PASS [1/%d] N=100 distinct: slow=%d ops, fast=%d ops, ratio=%.1fx (expected >10x)%n", total, sOps, fOps, ratio); pass++; } // Test 2: N=200 distinct — ratio should be ~100x { total++; int n = 200; List sigs = distinctSignatures(n); long sOps = slowCleanOverloads(sigs); long fOps = fastCleanOverloads(sigs); double ratio = (double) sOps / fOps; assert ratio > 50.0 : "solc-0001 expected >50x ratio at N=200; got " + ratio; System.out.printf(" PASS [2/%d] N=200 distinct: slow=%d ops, fast=%d ops, ratio=%.1fx (expected >50x)%n", total, sOps, fOps, ratio); pass++; } // Test 3: half-dup N=100 — slow still O(N²/4), fast O(N) { total++; int n = 100; List sigs = halfDupSignatures(n); long sOps = slowCleanOverloads(sigs); long fOps = fastCleanOverloads(sigs); double ratio = (double) sOps / fOps; assert ratio > 10.0 : "solc-0001 half-dup expected >10x ratio at N=100; got " + ratio; System.out.printf(" PASS [3/%d] N=100 half-dup: slow=%d ops, fast=%d ops, ratio=%.1fx (expected >10x)%n", total, sOps, fOps, ratio); pass++; } // Test 4: output correctness — both methods must agree on unique-count { total++; int n = 100; List sigs = halfDupSignatures(n); // count uniques via slow List slowUniques = new ArrayList<>(); for (String sig : sigs) { if (!slowUniques.contains(sig)) slowUniques.add(sig); } // count uniques via fast Set fastUniques = new LinkedHashSet<>(sigs); assert slowUniques.size() == fastUniques.size() : "solc-0001 output mismatch: slow=" + slowUniques.size() + " fast=" + fastUniques.size(); assert slowUniques.size() == n / 2 : "solc-0001 expected " + (n / 2) + " unique sigs; got " + slowUniques.size(); System.out.printf(" PASS [4/%d] Output correctness: both methods yield %d unique sigs from %d candidates%n", total, slowUniques.size(), n); pass++; } System.out.println(); System.out.printf("%d/%d PASS%n", pass, total); System.out.printf("ALL PASS%n"); System.out.println(); System.out.printf("solc-0001: TypeChecker::cleanOverloadedDeclarations — vector find_if O(N²) → unordered_set O(N)%n"); System.out.printf("Hotpath: every identifier resolution with overloaded declarations in Solidity source%n"); System.out.printf("solc-0002: ContractLevelChecker::findDuplicateDefinitions — CLEAN (CWE-407); set::count O(log N) acceptable for small N%n"); } }