java-topology/defects/linux/unit/Linux0005Test.java

382 lines
16 KiB
Java
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package unit;
import java.util.*;
/**
* Linux0005Test — CWE-407 benchmark for linux-0005 and linux-0006
*
* linux-0005 (COMPONENT_FIND_QUADRATIC):
* Models drivers/base/component.c find_components():
* SLOW: for each adev [O(A)]:
* for each match entry [O(M)]:
* find_component() scans component_list [O(C)]
* Total: O(A × M × C) per component_add()
* With N component_add() calls at boot: O(N × A × M × C) = O(N²) boot cost
* FAST: hash table keyed on dev pointer — O(1) lookup
* Total per component_add(): O(A × M)
*
* linux-0006 (BTF_MODULE_SCAN_LINEAR):
* Models kernel/bpf/btf.c bpf_find_btf_id():
* SLOW: idr_for_each_entry() scans all loaded module BTFs [O(M)] per kptr field
* A BPF map struct with F kptr fields → O(F × M) per BPF_MAP_CREATE syscall
* Kernel comment: "linear search could be slow"
* FAST: hash table keyed on (name_hash ^ kind) — O(1) amortised after first miss
* O(F) per BPF_MAP_CREATE after cache warm-up
*/
public class Linux0005Test {
// =========================================================================
// linux-0005: component find_components O(A×M×C) vs O(A×M)
// =========================================================================
/** Simulates struct component — one registered device component. */
static class Component {
final Object dev; // device pointer (any Object — we use identity)
Object boundAdev; // null if unbound
Component(Object dev) { this.dev = dev; }
}
/** One entry in a match array — holds the dev pointer to find. */
static class MatchEntry {
final Object devWanted; // the device this entry is looking for
Component component; // filled in when found
MatchEntry(Object devWanted) { this.devWanted = devWanted; }
}
/** Simulates struct aggregate_device. */
static class AggDev {
final MatchEntry[] match;
AggDev(MatchEntry[] match) { this.match = match; }
}
/**
* SLOW: find_component() — O(C) linear scan of component_list.
* Returns number of comparisons performed.
*/
static long findComponent_slow(List<Component> componentList,
AggDev adev,
MatchEntry mc) {
long ops = 0;
for (Component c : componentList) {
ops++;
if (c.boundAdev != null && c.boundAdev != adev)
continue;
// mc->compare(c->dev, mc->data) — identity comparison
if (c.dev == mc.devWanted) {
return ops;
}
}
return ops;
}
/**
* SLOW: find_components() — calls find_component() M times per adev.
* Outer loop over adevs: O(A × M × C).
* Returns total comparison count.
*/
static long findComponents_slow(List<Component> componentList,
List<AggDev> adevList) {
long ops = 0;
for (AggDev adev : adevList) {
for (MatchEntry mc : adev.match) {
if (mc.component != null) continue;
ops += findComponent_slow(componentList, adev, mc);
}
}
return ops;
}
/**
* FAST: hash table (IdentityHashMap as O(1) lookup) keyed on dev pointer.
* find_component() becomes a single map.get() call.
* Returns total comparison count (always 1 per match entry for a hit).
*/
static long findComponents_fast(Map<Object, Component> componentMap,
List<AggDev> adevList) {
long ops = 0;
for (AggDev adev : adevList) {
for (MatchEntry mc : adev.match) {
if (mc.component != null) continue;
ops++; // one hash probe
Component c = componentMap.get(mc.devWanted);
if (c != null && (c.boundAdev == null || c.boundAdev == adev)) {
// found
}
}
}
return ops;
}
// =========================================================================
// linux-0006: bpf_find_btf_id O(F×M) vs O(F) with cache
// =========================================================================
/** Simulates one module BTF — holds a flat array of type names. */
static class ModuleBtf {
final String moduleName;
final String[] typeNames;
ModuleBtf(String moduleName, String[] typeNames) {
this.moduleName = moduleName;
this.typeNames = typeNames;
}
/** O(T) linear scan — btf_find_by_name_kind for module BTF. */
int findByNameKind(String name, int kind) {
for (int i = 0; i < typeNames.length; i++) {
if (typeNames[i].equals(name)) return i + 1; // positive id
}
return -1;
}
}
/**
* SLOW: bpf_find_btf_id() — idr_for_each_entry over all module BTFs.
* For each kptr field: scan M module BTFs → O(F × M × T).
* Returns number of (module-BTF, field) scan iterations.
*/
static long findBtfId_slow(List<ModuleBtf> moduleBtfs,
String[] kptrFieldNames,
int kind) {
long ops = 0;
for (String fieldName : kptrFieldNames) {
// idr_for_each_entry walks all M module BTFs
for (ModuleBtf mbtf : moduleBtfs) {
ops++;
int id = mbtf.findByNameKind(fieldName, kind);
if (id > 0) break; // found — stop scanning
}
}
return ops;
}
/**
* FAST: name→id hash cache (HashMap as O(1) lookup).
* First lookup for a name misses and populates the cache; subsequent
* lookups are O(1). Returns total module-BTF iterations across all fields.
*
* Simulates: check vmlinux (O(log T) bsearch, modelled as O(1)),
* then check cache (O(1)), then fall through to O(M) scan on miss.
*/
static long findBtfId_fast(List<ModuleBtf> moduleBtfs,
String[] kptrFieldNames,
int kind,
Map<String, Integer> cache) {
long ops = 0;
for (String fieldName : kptrFieldNames) {
String cacheKey = fieldName + ":" + kind;
if (cache.containsKey(cacheKey)) {
ops++; // O(1) cache hit
continue;
}
// Cache miss — scan modules (first time only)
for (ModuleBtf mbtf : moduleBtfs) {
ops++;
int id = mbtf.findByNameKind(fieldName, kind);
if (id > 0) {
cache.put(cacheKey, id); // populate cache
break;
}
}
}
return ops;
}
// =========================================================================
// Harness
// =========================================================================
static void bench(String label, long sOps, long fOps, long minRatio) {
double ratio = fOps == 0 ? Double.MAX_VALUE : (double) sOps / fOps;
boolean pass = ratio >= minRatio;
System.out.printf(" %-60s slow=%,d fast=%,d ratio=%.1fx [%s]%n",
label, sOps, fOps, ratio, pass ? "PASS" : "FAIL");
}
// =========================================================================
// main
// =========================================================================
public static void main(String[] args) {
int passed = 0, total = 0;
System.out.println("Linux0005Test — CWE-407 (linux-0005 component, linux-0006 btf)");
System.out.println("=".repeat(76));
// ------------------------------------------------------------------
// linux-0005: component find_components quadratic
// ------------------------------------------------------------------
System.out.println("\nlinux-0005: component find_components O(A×M×C) vs O(A×M)");
{
// Realistic SoC: 80 components, 6 aggregate devices, 8 match entries each
int C = 80, A = 6, M = 8;
List<Object> devPtrs = new ArrayList<>(C);
for (int i = 0; i < C; i++) devPtrs.add(new Object());
List<Component> componentList = new ArrayList<>(C);
Map<Object, Component> componentMap = new IdentityHashMap<>(C * 2);
for (Object dev : devPtrs) {
Component comp = new Component(dev);
componentList.add(comp);
componentMap.put(dev, comp);
}
// Each adev matches the last M devices (worst-case: found at end of list)
List<AggDev> adevList = new ArrayList<>(A);
for (int a = 0; a < A; a++) {
MatchEntry[] matches = new MatchEntry[M];
for (int m = 0; m < M; m++) {
// Point at tail of the component list → worst case for linear scan
matches[m] = new MatchEntry(devPtrs.get(C - 1 - m));
}
adevList.add(new AggDev(matches));
}
// Simulate N component_add() events — each triggers find_components on all adevs
int N = 80;
long slowTotal = 0, fastTotal = 0;
for (int n = 0; n < N; n++) {
// Clear bound state so all matches are re-evaluated
for (AggDev ad : adevList)
for (MatchEntry me : ad.match) me.component = null;
slowTotal += findComponents_slow(componentList, adevList);
}
for (int n = 0; n < N; n++) {
for (AggDev ad : adevList)
for (MatchEntry me : ad.match) me.component = null;
fastTotal += findComponents_fast(componentMap, adevList);
}
// Expected: slow = N × A × M × avg_scan = 80 × 6 × 8 × ~(C/2) ≈ 153600
// fast = N × A × M × 1 = 80 × 6 × 8 = 3840
// Ratio ≈ C/2 = 40x
long expectedMinRatio = Math.max(5L, (long)(C / 4));
bench(String.format("SoC boot C=%d A=%d M=%d N=%d component_add events", C, A, M, N),
slowTotal, fastTotal, expectedMinRatio);
total++;
if (slowTotal > fastTotal * expectedMinRatio) passed++;
}
{
// Large display controller: 200 components, 12 adevs, 15 match entries
int C = 200, A = 12, M = 15;
List<Object> devPtrs = new ArrayList<>(C);
for (int i = 0; i < C; i++) devPtrs.add(new Object());
List<Component> componentList = new ArrayList<>(C);
Map<Object, Component> componentMap = new IdentityHashMap<>(C * 2);
for (Object dev : devPtrs) {
Component comp = new Component(dev);
componentList.add(comp);
componentMap.put(dev, comp);
}
List<AggDev> adevList = new ArrayList<>(A);
for (int a = 0; a < A; a++) {
MatchEntry[] matches = new MatchEntry[M];
for (int m = 0; m < M; m++)
matches[m] = new MatchEntry(devPtrs.get(C - 1 - m));
adevList.add(new AggDev(matches));
}
int N = 200;
long slowTotal = 0, fastTotal = 0;
for (int n = 0; n < N; n++) {
for (AggDev ad : adevList)
for (MatchEntry me : ad.match) me.component = null;
slowTotal += findComponents_slow(componentList, adevList);
}
for (int n = 0; n < N; n++) {
for (AggDev ad : adevList)
for (MatchEntry me : ad.match) me.component = null;
fastTotal += findComponents_fast(componentMap, adevList);
}
long expectedMinRatio = Math.max(5L, (long)(C / 4));
bench(String.format("Display ctrl C=%d A=%d M=%d N=%d component_add events", C, A, M, N),
slowTotal, fastTotal, expectedMinRatio);
total++;
if (slowTotal > fastTotal * expectedMinRatio) passed++;
}
// ------------------------------------------------------------------
// linux-0006: bpf_find_btf_id O(F×M) vs O(F) with cache
// ------------------------------------------------------------------
System.out.println("\nlinux-0006: bpf_find_btf_id O(F×M) vs O(F) with hash cache");
{
// 64 loaded kernel modules, BPF map struct with 10 kptr fields
int M = 64, F = 10;
int KIND = 22; // BTF_KIND_STRUCT
// Build module BTFs — the target type lives in the last module (worst case)
List<ModuleBtf> moduleBtfs = new ArrayList<>(M);
String[] kptrNames = new String[F];
for (int f = 0; f < F; f++) kptrNames[f] = "kptr_type_" + f;
for (int m = 0; m < M; m++) {
String[] types;
if (m == M - 1) {
// Last module holds all target types
types = Arrays.copyOf(kptrNames, F);
} else {
types = new String[]{"unrelated_type_" + m};
}
moduleBtfs.add(new ModuleBtf("module_" + m, types));
}
// Simulate 500 BPF_MAP_CREATE syscalls — each re-scans all kptr fields
int SYSCALLS = 500;
long slowTotal = 0, fastTotal = 0;
for (int s = 0; s < SYSCALLS; s++)
slowTotal += findBtfId_slow(moduleBtfs, kptrNames, KIND);
Map<String, Integer> cache = new HashMap<>();
for (int s = 0; s < SYSCALLS; s++)
fastTotal += findBtfId_fast(moduleBtfs, kptrNames, KIND, cache);
// slow: SYSCALLS × F × avg_M_scanned = 500 × 10 × 64 = 320000
// fast: first call = 500 × 10 × 64 (cold), subsequent = SYSCALLS-1 × F × 1
// ≈ 10 × 64 + 499 × 10 = 640 + 4990 = 5630 total for F=10 fields
// (cache warms on first SYSCALL, rest are O(F) hits)
// Actual fast ≈ F*M + (SYSCALLS-1)*F = 640+4990 = 5630
// Ratio ≈ 320000/5630 ≈ 56x
bench(String.format("BPF kptr M=%d modules F=%d fields SYSCALLS=%d", M, F, SYSCALLS),
slowTotal, fastTotal, 10L);
total++;
if (slowTotal > fastTotal * 10L) passed++;
}
{
// 200 modules, struct with 25 kptr fields, 1000 map-create events
int M = 200, F = 25, SYSCALLS = 1000;
int KIND = 22;
List<ModuleBtf> moduleBtfs = new ArrayList<>(M);
String[] kptrNames = new String[F];
for (int f = 0; f < F; f++) kptrNames[f] = "heavy_kptr_" + f;
for (int m = 0; m < M; m++) {
String[] types = (m == M - 1)
? Arrays.copyOf(kptrNames, F)
: new String[]{"stub_" + m};
moduleBtfs.add(new ModuleBtf("mod_" + m, types));
}
long slowTotal = 0, fastTotal = 0;
for (int s = 0; s < SYSCALLS; s++)
slowTotal += findBtfId_slow(moduleBtfs, kptrNames, KIND);
Map<String, Integer> cache = new HashMap<>();
for (int s = 0; s < SYSCALLS; s++)
fastTotal += findBtfId_fast(moduleBtfs, kptrNames, KIND, cache);
// slow: 1000 × 25 × 200 = 5,000,000
// fast: first miss = 25×200=5000, then 999×25=24975 → ≈ 30000
// ratio ≈ 166x
bench(String.format("BPF kptr M=%d modules F=%d fields SYSCALLS=%d", M, F, SYSCALLS),
slowTotal, fastTotal, 20L);
total++;
if (slowTotal > fastTotal * 20L) passed++;
}
System.out.println("\n" + passed + "/" + total + " PASS");
if (passed < total) System.exit(1);
}
}