package unit; import java.util.*; /** * Standalone unit tests for scikit-learn CWE-407 defects. * * sklearn-0001: HistGradientBoosting _check_categories — O(C×F) feature_names.index in loop * Simulates: for feature_name in categorical_features: feature_names.index(feature_name) * slow(): O(F) linear scan per categorical feature via indexOf — total O(C×F) * fast(): build HashMap[name→idx] once O(F), then O(1) per lookup — total O(F+C) * Assert: slowOps >= fastOps * 20 for F=1000 features, C=200 categorical */ public class SklearnTest { // ── sklearn-0001 ────────────────────────────────────────────────────────── /** * Slow path: List.indexOf(featureName) per categorical feature. * Each .indexOf() scans up to F entries. * * @param featureNames all feature names (length F) * @param categoricalNames names to look up (length C) * @return op count (each element comparison during indexOf = 1 op) */ static long slowCheckCategories(List featureNames, List categoricalNames) { long ops = 0; boolean[] isCategorical = new boolean[featureNames.size()]; for (String catName : categoricalNames) { // Linear scan from index 0 — mirrors Python list.index() for (int i = 0; i < featureNames.size(); i++) { ops++; if (featureNames.get(i).equals(catName)) { isCategorical[i] = true; break; } } } return ops; } /** * Fast path: build HashMap[name→index] once, then O(1) per lookup. * * @param featureNames all feature names (length F) * @param categoricalNames names to look up (length C) * @return op count (1 op per entry in build + 1 op per lookup) */ static long fastCheckCategories(List featureNames, List categoricalNames) { long ops = 0; boolean[] isCategorical = new boolean[featureNames.size()]; // Build index: O(F) Map nameToIdx = new HashMap<>(featureNames.size() * 2); for (int i = 0; i < featureNames.size(); i++) { nameToIdx.put(featureNames.get(i), i); ops++; } // Lookup: O(1) per categorical feature for (String catName : categoricalNames) { Integer idx = nameToIdx.get(catName); ops++; if (idx != null) { isCategorical[idx] = true; } } return ops; } static void testCheckCategories() { int F = 1000; // total features int C = 200; // categorical features (worst-case: all near the end of the list) List featureNames = new ArrayList<>(F); for (int i = 0; i < F; i++) featureNames.add("feature_" + i); // Categorical features chosen from the second half — maximizes scan depth List categoricalNames = new ArrayList<>(C); for (int i = F / 2; i < F / 2 + C; i++) categoricalNames.add("feature_" + i); long slowOps = slowCheckCategories(featureNames, categoricalNames); long fastOps = fastCheckCategories(featureNames, categoricalNames); System.out.printf( "sklearn-0001 F=%-5d C=%-4d slowOps=%-8d fastOps=%-6d ratio=%.1fx%n", F, C, slowOps, fastOps, (double) slowOps / fastOps ); assert slowOps > fastOps * 20 : "sklearn-0001 FAIL: expected slowOps > 20×fastOps, got " + slowOps + " vs " + fastOps; System.out.println("sklearn-0001 PASS"); } // ── main ────────────────────────────────────────────────────────────────── public static void main(String[] args) { int pass = 0, total = 1; try { testCheckCategories(); pass++; } catch (AssertionError e) { System.err.println(e.getMessage()); } System.out.printf("%n%d/%d PASS%n", pass, total); if (pass != total) System.exit(1); } }