java-topology/defects/solc/unit/SolcTest.java

242 lines
9.8 KiB
Java

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<size_t>::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<String> signatures) {
List<String> 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<String> signatures) {
Set<String> seenSignatures = new HashSet<>();
List<String> 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<String> distinctSignatures(int n) {
List<String> 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<String> halfDupSignatures(int n) {
List<String> 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<String> 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<String> 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<String> 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<String> 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<String> sigs = halfDupSignatures(n);
// count uniques via slow
List<String> slowUniques = new ArrayList<>();
for (String sig : sigs) {
if (!slowUniques.contains(sig)) slowUniques.add(sig);
}
// count uniques via fast
Set<String> 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");
}
}