219 lines
8.8 KiB
Java
219 lines
8.8 KiB
Java
package unit;
|
||
|
||
import java.util.ArrayList;
|
||
import java.util.HashSet;
|
||
import java.util.List;
|
||
import java.util.Set;
|
||
|
||
/**
|
||
* Models rustc's is_target_feature_call_safe() —
|
||
* checks whether all features required by the callee are present in the caller's body.
|
||
*
|
||
* SLOW: O(C × B) — for each callee feature, scan all body features linearly.
|
||
* FAST: O(C + B) — build a HashSet from body features once, then O(1) per callee lookup.
|
||
*
|
||
* CWE-407: compiler/rustc_middle/src/ty/context.rs:1323-1325
|
||
*/
|
||
public class TargetFeatureCallSafeAlgorithm {
|
||
|
||
// -------------------------------------------------------------------------
|
||
// Slow (defective) implementation — mirrors current rustc code
|
||
// -------------------------------------------------------------------------
|
||
|
||
static class SlowChecker {
|
||
long linearScans = 0;
|
||
|
||
/**
|
||
* Returns true if all callee features are present in bodyFeatures.
|
||
* O(callee.size() × body.size()) — Vec<TargetFeature> nested linear scan.
|
||
*/
|
||
boolean isSafe(List<String> calleeFeatures, List<String> bodyFeatures) {
|
||
for (String callee : calleeFeatures) {
|
||
boolean found = false;
|
||
for (String body : bodyFeatures) {
|
||
linearScans++;
|
||
if (body.equals(callee)) {
|
||
found = true;
|
||
break;
|
||
}
|
||
}
|
||
if (!found) return false;
|
||
}
|
||
return true;
|
||
}
|
||
}
|
||
|
||
// -------------------------------------------------------------------------
|
||
// Fast (fixed) implementation — HashSet for O(1) membership
|
||
// -------------------------------------------------------------------------
|
||
|
||
static class FastChecker {
|
||
long hashLookups = 0;
|
||
|
||
/**
|
||
* Returns true if all callee features are present in bodyFeatures.
|
||
* O(C + B) — build HashSet once, then O(1) per callee feature.
|
||
*/
|
||
boolean isSafe(List<String> calleeFeatures, List<String> bodyFeatures) {
|
||
Set<String> bodySet = new HashSet<>(bodyFeatures);
|
||
for (String callee : calleeFeatures) {
|
||
hashLookups++;
|
||
if (!bodySet.contains(callee)) return false;
|
||
}
|
||
return true;
|
||
}
|
||
}
|
||
|
||
// -------------------------------------------------------------------------
|
||
// Test helpers
|
||
// -------------------------------------------------------------------------
|
||
|
||
static int passed = 0;
|
||
static int total = 0;
|
||
|
||
static void check(String desc, boolean cond) {
|
||
total++;
|
||
if (cond) {
|
||
passed++;
|
||
System.out.printf(" PASS %s%n", desc);
|
||
} else {
|
||
System.out.printf(" FAIL %s%n", desc);
|
||
}
|
||
}
|
||
|
||
/** Simulate x86-64 target features (subset of actual rustc list). */
|
||
static List<String> buildPlatformFeatures(int count) {
|
||
String[] base = {
|
||
"avx512f", "avx512cd", "avx512bw", "avx512dq", "avx512vl",
|
||
"avx512ifma", "avx512vbmi", "avx512vnni", "avx512bf16", "avx512vp2intersect",
|
||
"avx2", "avx", "fma", "bmi1", "bmi2", "lzcnt", "popcnt",
|
||
"sse4.1", "sse4.2", "sse4a", "ssse3", "sse3", "sse2", "sse",
|
||
"aes", "pclmulqdq", "sha", "rdrnd", "rdseed", "adx",
|
||
"f16c", "fxsr", "xsave", "xsaveopt", "xsavec", "xsaves",
|
||
"cx16", "sahf", "movbe", "cmpxchg16b", "clflushopt", "clwb",
|
||
"rtm", "hle", "tbm", "3dnow", "3dnowa", "mmx", "abm",
|
||
"ermsb", "fsrm", "vpclmulqdq", "vaes", "gfni", "avxvnni",
|
||
"amx-tile", "amx-int8", "amx-bf16", "amx-fp16",
|
||
"serialize", "tsxldtrk", "uintr", "kl", "widekl",
|
||
};
|
||
List<String> result = new ArrayList<>();
|
||
for (int i = 0; i < Math.min(count, base.length); i++) {
|
||
result.add(base[i]);
|
||
}
|
||
return result;
|
||
}
|
||
|
||
public static void main(String[] args) {
|
||
System.out.println("TargetFeatureCallSafeAlgorithm — CWE-407 unit test");
|
||
System.out.println("rustc-0003: is_target_feature_call_safe O(C×B) → O(C+B)");
|
||
System.out.println();
|
||
|
||
SlowChecker slow = new SlowChecker();
|
||
FastChecker fast = new FastChecker();
|
||
|
||
// --- Correctness: safe call (callee subset of body) ---
|
||
{
|
||
List<String> callee = List.of("avx2", "bmi1", "popcnt");
|
||
List<String> body = List.of("avx2", "avx", "bmi1", "bmi2", "popcnt", "sse4.1");
|
||
|
||
boolean slowResult = slow.isSafe(callee, body);
|
||
boolean fastResult = fast.isSafe(callee, body);
|
||
|
||
check("safe-call: slow returns true", slowResult);
|
||
check("safe-call: fast returns true", fastResult);
|
||
check("safe-call: results agree", slowResult == fastResult);
|
||
}
|
||
|
||
// --- Correctness: unsafe call (callee has feature body lacks) ---
|
||
{
|
||
List<String> callee = List.of("avx512f", "avx512bw");
|
||
List<String> body = List.of("avx2", "avx", "bmi1");
|
||
|
||
boolean slowResult = slow.isSafe(callee, body);
|
||
boolean fastResult = fast.isSafe(callee, body);
|
||
|
||
check("unsafe-call: slow returns false", !slowResult);
|
||
check("unsafe-call: fast returns false", !fastResult);
|
||
check("unsafe-call: results agree", slowResult == fastResult);
|
||
}
|
||
|
||
// --- Correctness: empty callee (always safe) ---
|
||
{
|
||
List<String> callee = List.of();
|
||
List<String> body = List.of("avx2");
|
||
|
||
boolean slowResult = slow.isSafe(callee, body);
|
||
boolean fastResult = fast.isSafe(callee, body);
|
||
|
||
check("empty-callee: slow returns true", slowResult);
|
||
check("empty-callee: fast returns true", fastResult);
|
||
}
|
||
|
||
// --- Correctness: empty body, non-empty callee (always unsafe) ---
|
||
{
|
||
List<String> callee = List.of("avx2");
|
||
List<String> body = List.of();
|
||
|
||
boolean slowResult = slow.isSafe(callee, body);
|
||
boolean fastResult = fast.isSafe(callee, body);
|
||
|
||
check("empty-body: slow returns false", !slowResult);
|
||
check("empty-body: fast returns false", !fastResult);
|
||
}
|
||
|
||
// --- Performance: O(C×B) vs O(C+B) ---
|
||
{
|
||
// Simulate a function with 50 callee features all present at the END of a 60-feature
|
||
// body — forces slow checker to scan to the last position for each callee feature,
|
||
// maximising the per-call comparison count to ~C×B.
|
||
int C = 50;
|
||
int B = 60;
|
||
// callee features are the LAST C entries of the B-element body list
|
||
List<String> fullBody = buildPlatformFeatures(B);
|
||
List<String> body = new ArrayList<>(fullBody);
|
||
// callee = last C elements (appear near end of body, maximise scan length)
|
||
List<String> callee = new ArrayList<>(fullBody.subList(B - C, B));
|
||
// Reverse body so callee features are at the back
|
||
java.util.Collections.reverse(body);
|
||
|
||
// Warm up JIT
|
||
for (int i = 0; i < 500; i++) slow.isSafe(callee, body);
|
||
for (int i = 0; i < 500; i++) fast.isSafe(callee, body);
|
||
|
||
// Reset counters
|
||
slow.linearScans = 0;
|
||
fast.hashLookups = 0;
|
||
|
||
int RUNS = 20_000;
|
||
long t0 = System.nanoTime();
|
||
for (int i = 0; i < RUNS; i++) slow.isSafe(callee, body);
|
||
long slowNs = System.nanoTime() - t0;
|
||
|
||
long t1 = System.nanoTime();
|
||
for (int i = 0; i < RUNS; i++) fast.isSafe(callee, body);
|
||
long fastNs = System.nanoTime() - t1;
|
||
|
||
double ratio = (double) slowNs / fastNs;
|
||
|
||
System.out.printf(" INFO C=%d B=%d slow_scans=%d fast_lookups=%d ratio=%.1fx%n",
|
||
C, B, slow.linearScans, fast.hashLookups, ratio);
|
||
|
||
// Slow: each callee feature is at the back of body → scans many positions each.
|
||
// Minimum acceptable: at least C*(B/4)*RUNS (conservative lower bound, early-exit aware)
|
||
check("slow does >> C×RUNS comparisons (linear scan dominates)",
|
||
slow.linearScans >= (long) C * (B / 4) * RUNS);
|
||
// Fast: exactly C hash lookups per call
|
||
check("fast does exactly C×RUNS hash lookups",
|
||
fast.hashLookups == (long) C * RUNS);
|
||
// Op ratio: slow does at least B/2 per callee feature, fast does 1
|
||
long slowOpsPerRun = slow.linearScans / RUNS;
|
||
long fastOpsPerRun = fast.hashLookups / RUNS;
|
||
check("slow op count >> fast op count (>= 10x)",
|
||
slowOpsPerRun >= fastOpsPerRun * 10);
|
||
}
|
||
|
||
System.out.println();
|
||
System.out.printf("%d/%d PASS%n", passed, total);
|
||
if (passed != total) System.exit(1);
|
||
}
|
||
}
|