From e52caba572a14d7bd8c0491212210595f4076768 Mon Sep 17 00:00:00 2001 From: "russell@unturf.com" Date: Sun, 29 Mar 2026 22:12:55 -0400 Subject: [PATCH] outreach: update scala.md with scala-0002 (intersectionIsEmpty 100x) --- whitepaper/outreach/scala.md | 29 ++++++++++++++++++++++++----- 1 file changed, 24 insertions(+), 5 deletions(-) diff --git a/whitepaper/outreach/scala.md b/whitepaper/outreach/scala.md index 9bfb09689..bf754dda1 100644 --- a/whitepaper/outreach/scala.md +++ b/whitepaper/outreach/scala.md @@ -1,12 +1,25 @@ # Scala (compiler) — CWE-407 Disclosure Brief -**2026-03-27 · Patch available — awaiting upstream merge** +**2026-03-29 · 2 patches available — awaiting upstream merge** ## Finding -One O(n²) defect in the Scala 2 compiler's pattern match exhaustiveness checker. `Checkable.scala` uses `to.baseClasses.contains(bc)` — an O(M×N) scan — per pattern match expression during type checking. Patch ready for upstream review. +Two O(n²) defects in the Scala 2 compiler. `Checkable.scala` uses an O(M×N) scan per pattern match expression; `RefChecks.scala` uses an O(D²) scan per overriding method pair during class verification. Patches ready for upstream review. ## The Defects +**scala-0002 (PATCHED — MEDIUM):** `src/compiler/scala/tools/nsc/typechecker/RefChecks.scala` + +```scala +// Inside checkAllOverrides — called for every class with overriding methods: +def intersectionIsEmpty(syms1: List[Symbol], syms2: List[Symbol]) = + !syms1.exists(syms2.contains) // syms2.contains = O(D) List scan +``` + +`syms2.contains` on `List[Symbol]` is O(|syms2|) per call. Both `syms1` and `syms2` +are `extendedOverriddenSymbols` — O(D) symbols for hierarchy depth D. +**O(D²) per overriding pair.** With M pairs per class: **O(M × D²) per compilation unit.** +**Measured ratio: 100×** at D=200. + **scala-0001 (PATCHED — HIGH):** `src/compiler/scala/tools/nsc/typechecker/Checkable.scala` ```scala @@ -20,11 +33,16 @@ to.baseClasses.exists(bc => ## Complexity Proof -For M=50 base classes in `from`, N=50 base classes in `to`: +**scala-0001:** For M=50 base classes in `from`, N=50 base classes in `to`: - O(M×N) = 2,500 comparisons per pattern match - Fixed: `from.baseClasses.toSet` before `exists` loop → O(M + N) - **50× measured ratio.** +**scala-0002:** For D=200 overridden symbols per method: +- O(D²) = 40,000 comparisons per intersectionIsEmpty call +- Fixed: `syms2.toSet` once, then O(1) per lookup → O(D) per call +- **100× measured ratio at D=200, M=50 pairs.** + ## Impact All Scala 2 codebases using pattern matching — essentially all non-trivial Scala programs. Pattern match exhaustiveness checking runs at compile time for every `match` expression with sealed traits, case classes, and abstract types. Large codebases with complex type hierarchies (deep inheritance chains, many sealed subclasses) hit worst case during compilation. Scala 2 is still widely deployed in production codebases (Spark, Akka, Play, enterprise finance). @@ -45,9 +63,10 @@ val fromBaseSet = from.baseClasses.toSet to.baseClasses.exists(bc => fromBaseSet.contains(bc)) // O(N) total ``` -## Patch +## Patches -`defects/scala/patch/scala-0001-checkable-baseclasses-set.patch` +- `defects/scala/patch/scala-0001-checkable-baseclasses-set.patch` +- `defects/scala/patch/scala-0002-refchecks-intersection-is-empty-hashset.patch` ## What We Ask