package unit; import support.Moad0011Algorithm; import support.Moad0011Algorithm.Result; /** * Unit tests for MOAD-0011: CWE-1333 — A Catastrophic Inheritance. * * Proves from first principles: * 1. Structural: hasCatastrophicStructure() detects nested quantifiers in * known-bad patterns and passes known-safe patterns. * 2. Behavioral (step counting): defective NFA for (a+)+b explores * exponentially many states on adversarial input; fixed NFA is linear. * 3. Growth: doubling adversarial input length more than doubles defective * steps (super-linear / exponential growth); fixed steps grow exactly linearly. * 4. Both produce identical match results on matching and non-matching inputs. * * The step-counting NFA proves the algorithmic complexity without relying on * wall-clock timing — suitable for deterministic unit testing. Functional tests * use a timed match on a moderate adversarial input to prove behavioral impact. * * No build tool required. Compile and run: * * cd tests * java -m jdk.compiler/com.sun.tools.javac.Main -cp . \ * support/Moad0011Algorithm.java unit/Moad0011UnitTest.java * java -cp . unit.Moad0011UnitTest */ public class Moad0011UnitTest { private static int passed = 0; private static int failed = 0; public static void main(String[] args) { System.out.println("=== Moad0011UnitTest (A Catastrophic Inheritance) ===\n"); System.out.println("-- Structural detection: hasCatastrophicStructure() --"); testDetectsKnownBadPatterns(); testPassesKnownSafePatterns(); System.out.println("\n-- Correctness: both produce identical match results --"); testBothMatchMatchingInput(); testBothRejectNonMatchingInput(); testBothMatchSingleA(); System.out.println("\n-- Defect: step count grows super-linearly on adversarial input --"); testDefectiveStepCountExponentialGrowth(); testDefectiveAdversarialN8IsLarge(); System.out.println("\n-- Fix: step count grows linearly on adversarial input --"); testFixedStepCountLinearGrowth(); testFixedAdversarialN8IsSmall(); System.out.println("\n-- Ratio: defective steps >> fixed steps on adversarial input --"); testStepRatioAdvantageForFixed(); System.out.printf("\n%d passed, %d failed%n", passed, failed); if (failed > 0) System.exit(1); } // ── Structural detection ────────────────────────────────────────────────── static void testDetectsKnownBadPatterns() { String[] bad = { "(a+)+b", // classic ReDoS — outer + wraps inner a+ "(a+)+", // same, no terminal "(a*)+", // inner * outer + "(ab+c*)+", // complex inner group with quantifiers Moad0011Algorithm.DEFECTIVE_PATTERN_BLEACH, Moad0011Algorithm.DEFECTIVE_PATTERN_CSS, }; for (String p : bad) { assertTrue("detects bad pattern: " + p, Moad0011Algorithm.hasCatastrophicStructure(p), "expected catastrophic, got safe"); } } static void testPassesKnownSafePatterns() { String[] safe = { "a+b", // simple concatenation — no nesting "a+", // single quantifier "(a|b)+", // alternation with outer +, no inner quantifier "[a-z]+", // character class, single quantifier "(abc)+", // fixed group, no inner quantifier Moad0011Algorithm.FIXED_PATTERN_BLEACH, Moad0011Algorithm.FIXED_PATTERN_CSS, }; for (String p : safe) { assertFalse("passes safe pattern: " + p, Moad0011Algorithm.hasCatastrophicStructure(p), "expected safe, got catastrophic"); } } // ── Correctness ─────────────────────────────────────────────────────────── static void testBothMatchMatchingInput() { String input = Moad0011Algorithm.matchingInput(5); // "aaaaab" Result def = Moad0011Algorithm.matchDefective(input); Result fix = Moad0011Algorithm.matchFixed(input); assertTrue("defective: matches 'aaaaab'", def.matched, "expected match"); assertTrue("fixed: matches 'aaaaab'", fix.matched, "expected match"); } static void testBothRejectNonMatchingInput() { String input = Moad0011Algorithm.adversarialInput(5); // "aaaaac" — no 'b' Result def = Moad0011Algorithm.matchDefective(input); Result fix = Moad0011Algorithm.matchFixed(input); assertFalse("defective: rejects 'aaaaac'", def.matched, "expected no match"); assertFalse("fixed: rejects 'aaaaac'", fix.matched, "expected no match"); } static void testBothMatchSingleA() { String input = "ab"; Result def = Moad0011Algorithm.matchDefective(input); Result fix = Moad0011Algorithm.matchFixed(input); assertTrue("defective: matches 'ab'", def.matched, "expected match"); assertTrue("fixed: matches 'ab'", fix.matched, "expected match"); } // ── Defect ──────────────────────────────────────────────────────────────── static void testDefectiveStepCountExponentialGrowth() { // Adversarial input: N a's + 'c' — non-matching, forces maximum backtracking long steps4 = Moad0011Algorithm.matchDefective(Moad0011Algorithm.adversarialInput(4)).steps; long steps6 = Moad0011Algorithm.matchDefective(Moad0011Algorithm.adversarialInput(6)).steps; long steps8 = Moad0011Algorithm.matchDefective(Moad0011Algorithm.adversarialInput(8)).steps; // Exponential growth: steps8 >> steps6 >> steps4 // Ratio should be super-linear (greater than linear doubling) double ratio64 = (double) steps6 / steps4; double ratio86 = (double) steps8 / steps6; assertTrue("defective: steps grow super-linearly from N=4 to N=6 (ratio > 2.0)", ratio64 > 2.0, "ratio64=" + ratio64); assertTrue("defective: steps grow super-linearly from N=6 to N=8 (ratio > 2.0)", ratio86 > 2.0, "ratio86=" + ratio86); System.out.printf(" steps N=4: %d, N=6: %d, N=8: %d (ratios: %.1fx, %.1fx)%n", steps4, steps6, steps8, ratio64, ratio86); } static void testDefectiveAdversarialN8IsLarge() { long steps = Moad0011Algorithm.matchDefective(Moad0011Algorithm.adversarialInput(8)).steps; // N=8 adversarial input: expect hundreds or thousands of steps (super-linear) assertTrue("defective: N=8 adversarial input requires many steps (>50)", steps > 50, "steps=" + steps); } // ── Fix ─────────────────────────────────────────────────────────────────── static void testFixedStepCountLinearGrowth() { long steps4 = Moad0011Algorithm.matchFixed(Moad0011Algorithm.adversarialInput(4)).steps; long steps8 = Moad0011Algorithm.matchFixed(Moad0011Algorithm.adversarialInput(8)).steps; long steps16 = Moad0011Algorithm.matchFixed(Moad0011Algorithm.adversarialInput(16)).steps; // Linear growth: doubling N should roughly double steps (ratio ~2x) double ratio84 = (double) steps8 / steps4; double ratio168 = (double) steps16 / steps8; // Allow ratio up to 3.0 to be generous with small constants, but it must not be exponential assertTrue("fixed: doubling N from 4 to 8 roughly doubles steps (ratio 1.0-3.0)", ratio84 >= 1.0 && ratio84 <= 3.0, "ratio84=" + ratio84); assertTrue("fixed: doubling N from 8 to 16 roughly doubles steps (ratio 1.0-3.0)", ratio168 >= 1.0 && ratio168 <= 3.0, "ratio168=" + ratio168); System.out.printf(" steps N=4: %d, N=8: %d, N=16: %d (ratios: %.1fx, %.1fx)%n", steps4, steps8, steps16, ratio84, ratio168); } static void testFixedAdversarialN8IsSmall() { long steps = Moad0011Algorithm.matchFixed(Moad0011Algorithm.adversarialInput(8)).steps; // Fixed NFA: O(N) steps — N=8 means steps should be a small multiple of 8 assertTrue("fixed: N=8 adversarial requires <= 20 steps (linear)", steps <= 20, "steps=" + steps); } // ── Ratio ───────────────────────────────────────────────────────────────── static void testStepRatioAdvantageForFixed() { String adversarial = Moad0011Algorithm.adversarialInput(10); long defSteps = Moad0011Algorithm.matchDefective(adversarial).steps; long fixSteps = Moad0011Algorithm.matchFixed(adversarial).steps; // At N=10, defective explores exponentially many states vs fixed linear scan double ratio = (double) defSteps / fixSteps; assertTrue("defective steps >> fixed steps at N=10 adversarial (ratio > 10x)", ratio > 10.0, "defSteps=" + defSteps + " fixSteps=" + fixSteps + " ratio=" + ratio); System.out.printf(" N=10 adversarial: defective=%d steps, fixed=%d steps (%.0fx advantage)%n", defSteps, fixSteps, ratio); } // ── Helpers ─────────────────────────────────────────────────────────────── static void assertTrue(String label, boolean condition, String detail) { if (condition) { System.out.printf(" PASS: %s%n", label); passed++; } else { System.out.printf(" FAIL: %s — %s%n", label, detail); failed++; } } static void assertFalse(String label, boolean condition, String detail) { assertTrue(label, !condition, detail); } }