Skip to content

candor-java v0.40.16

Choose a tag to compare

@tombaldwin tombaldwin released this 24 Oct 18:10
· 24 commits to main since this release

[0.40.16] — 2026-10-24

  • ⚠ A chained toString/hashCode/equals/compareTo on a dependency class that INHERITS the member takes the
    override walk too
    (SOUNDNESS R1378). A dependency class that does not declare toString inherits Object's, so
    javac spells p.toString() as invokevirtual java/lang/Object.toString, and R1360's walk — which runs for an owner a
    dependency declares — never saw the site: go(dep.P p) { return p.toString(); } over thirteen overrides, one
    reading the environment, read ABSENT, deny Env Unknown 0 where the one-tree scan reads 1 (v0.40.14, v0.40.15;
    executed). The receiver's DECLARED type is the dependency's class, which §4's MAY does not cover, so the walk now
    runs from it, under R1360's bound and rule: [Unknown]{dispatch:dep.P.toString} = one-tree. Kill switch:
    CANDOR_R1378=0.

  • R1336's count only resolves a site when it can be answered; where it cannot, the dependency's own union entry
    decides
    (SPEC PART 30 P6: degrading a sidecar may only widen a disclosure). The first cut fell back to the override
    ROWS when the paired call-graph sidecar was missing, unpaired, unreadable or incomplete, so a broad site unioned
    instead of hedging: deleting an optional file made the chained verdict MORE resolved. Now the sidecar counts only
    when it is the report's stem sibling and its key set reproduces the report's ⟨0.21⟩ analyzed manifest (count and
    digest — true of all 304 corpus dependencies); the count is an interval (decided subtypes .. plus undecided ones,
    the hierarchy bounding it), and only an interval straddling the bound is unanswerable. There the verdict is the
    owner's union entry when it has one (a broad union's hedge, a narrow union's effects, a pure narrow union stays
    pure — PART 92 c3_pure_only), and otherwise the rows unioned beside a dispatch: hedge. An overloaded owner's bare
    qual is undecided, not "declares nothing". An unreadable sidecar no longer fails the run; it is unanswerable, with
    a note. Executed fixture (fxP6; Sub0.parse writes the file it is handed), cells deny Fs / deny Unknown /
    deny Fs Unknown, sidecar present / deleted / half its keys dropped / unparseable / unpaired: concrete owner, 13
    subtypes, 1 charged — 8cb94c8–aa5c880 011 / 101 / 101 / exit 2 / 011, this build 011 / 111 / 111 / 111 / 111;
    abstract owner, 13 subtypes — 011 in every arm (the union entry's hedge); concrete owner, 13 pure — 011 in
    every arm (8cb94c8: 000 degraded); narrow pure union (abstract, 3 pure) — 000 in every arm; 6 subtypes —
    101 in every arm. A sixth arm drops ONLY pure units' keys (the case a dependency-row test cannot see): this build
    111 / 011 on the two 13-subtype concrete shapes, aa5c880 101 / 000. An owner's own-key dispatch: hedge is
    taken as the producer's verdict only when the owner has one member of that name (from the sidecar, or from the
    row's own disambiguated qual): dispatch: carries no descriptor, and log4j's real LogEventPatternConverter .format(Object, StringBuilder) body hedges its call to the other overload. Corpus (v0.40.15 → this build,
    chained 304 entries, every sidecar paired):
    the change is visible only through overloaded owners — 2,401 walked
    sites have undecided subtypes, 218 are unanswerable (184 rows unioned beside a hedge, 34 decided by a synthetic union
    entry). Against aa5c880: 18 rows change, 16 in reasons only; 2 lose concrete effects (commons-math3
    AbstractUnivariateStatistic.evaluate Rand; h2-mvstore BasicDataType.write Clock, Fs, Rand), each following
    the dependency's own union entry for that descriptor, each keeping Unknown, each absent from the published
    one-tree scan; h2-mvstore BasicDataType also loses 2 incomplete values with them. Standalone: byte-identical.
    Two boundary shapes found in review (Fable REVIEW3) are closed: (F1) an own-key dispatch: hedge stands in for
    the union only where a paired, complete sidecar covers the owner and every dependency ancestor and shows no overload
    on any of them — an overload declared on an ANCESTOR left the owner's qual bare, and with the sidecar deleted W's
    Fs was dropped (deny Fs 0 against 1 present, 1 one-tree); (F2) the bound counts the owner's own body only when it
    has one (in-scan declaresConcrete), read from the producer's synthetic union entry or pure-union key — an
    abstract member with exactly 12 declaring subtypes hedged with the sidecar present where the one-tree scan and the
    producer's NARROW union do not (tomcat-embed-el ELResolver.getCommonPropertyType, a pure union, hedged). Executed
    fixtures ovl2, x12-0, x12-1: present = deleted = keys-dropped = pure-dropped = unparseable = unpaired =
    one-tree in every one. A report with ⟨0.21⟩ unanalyzed units grants no sidecar coverage (its unanalysed types
    cannot show their declarations; SOUNDNESS R1371, census reach 0). CANDOR_R1336=0 is the UNANSWERABLE arm, not
    v0.40.15's row count. The three overload/coverage decisions are now ONE rule (depLineState: every type on the
    owner's line — chained, project or JDK — must be read, none may declare the name under another descriptor, and at
    most one dependency declaration's descriptor may be unknown, then only with no other dependency declaration beside
    it). It decides both whether a subtype's bare qual counts and whether an own-key hedge stands in for the union. It
    closes Fable REVIEW4 F3 (the ancestor in its own report, its hierarchy or call-graph sidecar deleted: deny Fs 1 →
    0) and two shapes the family fixture found beyond it: an overload SPLIT across classes (A.format(String) a pure
    body, P.format(Object)), and a producer's foreign union entry keyed under an ancestor for a member that ancestor
    never declared (only real rows count as evidence). Executed family fixture: overload on the owner / the ancestor /
    split / nowhere × ancestor in the same or its own report × each call-graph sidecar deleted or unpaired and the
    ancestor's whole report deleted — present equals one-tree in all 8 layouts, every degraded arm charges everything
    present does. NOT closed, pre-existing on v0.40.15 identically: deleting the OWNER's hierarchy sidecar leaves no walk
    at all ([Unknown] where present reads [Fs, Unknown]; an absent row where present hedges) — shipping knowingly as
    SOUNDNESS R1377
    : the state is detectable (a report with rows for types no hierarchy sidecar lists), the union-entry
    fallback cannot close it for a pure concrete owner (nothing is published under the key), and the INCOMPLETE /
    invisible: [pkg] path is the mechanism to evaluate. A bare qual is now undecided beside ANY other declaration of
    the name on the line, a PLATFORM one included (Fable REVIEW5 CARVE-A, the family's 9th layout: P extends Thread { run(int) }, thirteen run(int) subtypes and W's run() writing a file — the bare run read as run(), every
    subtype counted, and W's Fs was dropped with the sidecar PRESENT, against one-tree and v0.40.15; now [Fs, Unknown], wider than one-tree, never narrower). A wrongly CLEAN line counts EVERY bare subtype, not one, which is
    why the case is closed rather than carried. SOUNDNESS R1372 (boundary residuals, present = degraded, reasoned):
    a pure concrete owner body whose nearest published entry is an ancestor's synthetic union counts no owner body, and
    a pure-only default method with a body likewise — each under-counts by one at exactly 12.

  • ⚠ A chained toString/hashCode/equals/compareTo on a dependency's own class is bounded like every other
    member
    (SOUNDNESS R1360, SPEC §4 ⟨0.40⟩). The consumer's walk down a dependency class's subclasses returned
    NOTHING past the 12-implementor bound for the §4 Object protocol, so go(dep.Node n) { return n.toString(); }
    over 13 overrides that read the environment read ABSENT: deny Env, deny Unknown and deny Env Unknown all
    exited 0 (v0.40.14 and v0.40.15), where the one-tree scan of the same code hedges. Executed: go(new N0()) prints
    $HOME. §4's MAY covers a receiver typed by the root type itself; this walk only runs for a receiver typed by a
    class the dependency declares. Now: under the bound the overrides are unioned as before; past it the site reads
    Unknown[dispatch:<owner>.<member>] where some override is charged, and stays pure where every override is
    (the in-scan floor's rule). The count is R1336's (overriding subtypes). Kill switch: CANDOR_R1360=0.
    Measured against v0.40.15, chained census (304 entries): the protocol hedges at 48 sites in 36 consumer units
    of 20 jars; the absent-row shape itself does not occur in the census (every reached unit already carried
    Unknown), so no deny Unknown cell moves. It changes 23 rows: every new dispatch: reason is on a site the walk
    hedged (23/23). The trade: 23 concrete deny E cells go 1 → 0 in 5 rows — bouncycastle ASN1Primitive.hashCode
    ×4 (bc-fips, bc-fips-debug, bcprov, debugger-app) and jgit TreeFilter.toString — each where v0.40.15 unioned
    ≤12 effectful override rows over more than 12 overriding subtypes; each row keeps Unknown with the hedge on the
    lost site, and every lost cell is absent from both one-tree scans. Plus 11 class cells (indirect 5, reflect 4,
    native 2) on those rows and on guava/curator-shaded CharMatcher.toString, and the dropped overrides' surfaces:
    incomplete 10 values (bouncycastle ASN1Primitive ×3 jars, 3 each; jgit TreeFilter 1), netClass 3
    (ASN1Primitive ×3) and paths 1 (debugger-app ASN1Primitive), each unit keeping Unknown on the lost site.
    Standalone: byte-identical reports.
    Shipping knowingly (pre-existing on v0.40.15): an override whose row is [] with only invisible reads as pure
    — past the bound because the in-scan floor does the same (SOUNDNESS R1361), and under it because a chained consumer
    does not inherit a dependency row's invisible (SOUNDNESS R1362).

  • ⚠ PROVISIONAL — rides on a SPEC §3.1 clause under revision (a write-time merged union entry's unknownWhy is not
    direct-only; the code implements its rules 1–2: contributors' classes on the merged row, scan route seeded before
    propagation).
    A write-time merged union entry carries its contributors' reasons, and both gate routes read
    them
    (D1; SOUNDNESS R1330, R1338). The producer merges an interface's implementers into a real row claiming the
    same hash (a default method, a kept abstract member). The merge carried the implementers' EFFECTS, Unknown
    included, but only a BROAD merge's own dispatch: hedge as a reason; a narrow merge's Unknown arrived with no
    account. Every reader then made one up: a chained consumer's dep: catch-all read it as unresolved (R1330 — 38
    consumer cells on v0.40.15), and the producer's own scan route read the merged unit as unresolved while
    gate --report refused the narrowed rule. Separately, the broad hedge the row did carry reached an exact
    I.super.m() caller on gate --report only (R1338: deny Unknown[dispatch] p.Ex.call scan 0 / report 1). The
    merge now carries each contributor's transitive classes (R1239's projection, as for a union entry), and the scan
    route's class fixpoint is seeded with the written reasons before propagation. Executed fixture (r1330:
    RPlug.init reads $HOME by reflection, the default writes the file): the merged row now reads reflect, the
    chained consumer reads {reflect} where v0.40.15 read {reflect, unresolved}, and every narrowed policy is
    byte-equal on both routes (v0.40.15: 6 of 7 policies differed on that producer). The cost, the same on both
    routes: an exact caller of a merged default that already carries Unknown gains the merge's CLASS (never its
    effect) — PShape.plain reads reflect, Ex.call reads dispatch. Kill switch: CANDOR_D1=0.
    Measured against v0.40.15 (JDK 21). Standalone, 452 jars: inferred CHANGED 0, ADDED 0, REMOVED 0; 6,837
    merged rows in 165 jars change unknownWhy only, no field loses a value. Producer gate over the 161 jars that
    merge, one policy of five narrowed deny Unknown[…] rules: scan --policy and gate --report differed on 152
    jars, now on 2 (guava and curator's shaded guava — 211 units, the same 211 on v0.40.15, a separate pre-existing
    mechanism); gate --report refused 760 rule evaluations over reasonless merged rows, now 0; of 2,713 merged units
    the scan route read as implicitly unresolved, 2,238 now carry their contributors' classes and 475 keep
    unresolved (a contributor's framework dep: hedge — see below). The cost, scan route: 238 non-merged units in 9 jars
    (0.014% of 1,669,927 analysed) gain a class through an exact edge into a merged row — reflect 232,
    indirect 88, unresolved 24 cells; scala-library ×2 is 184 of them. Chained, 304 entries: 2,038 consumer cells
    lose unresolved — the REMOVING direction for a reason class, accounted for removal by removal: the dep:
    catch-all the consumer synthesised for a merged row's unaccounted Unknown is gone because the row now carries
    its contributors' classes (stripping them from the report brings it back); no consumer violation is left
    classless (16,170 of 16,170 carry a class in both arms); ~2,020 of the units gain nothing because the true classes
    were already there. 0 effects move from D1. The 475 merged units that keep unresolved keep it TRULY: their
    contributors' own reason is a framework dep: hedge on an off-jar call (e.g. amqp-client AMQChannel's
    dep:com.fasterxml.jackson.databind.ObjectMapper.readValue), for which unresolved is the right class.

  • ⚠ A chained call on a dependency class member hedges by the number of overriding SUBTYPES, not override ROWS
    (SOUNDNESS R1336, SPEC §4 bounded CHA). The consumer's walk down a dependency's subclasses unions their overrides
    up to the 12-implementor bound and past it reads Unknown[dispatch:…]. It counted the overrides that published
    a ROW, and a pure override publishes none (§2 rule 3), so the verdict followed the producer's precision rather
    than the hierarchy: zookeeper's CliCommand.parse has 23 overriding subtypes; v0.40.14 published 1 effectful
    row (the consumer unioned Fs, Log, Net) and v0.40.15 published 21 (it hedged), for one unchanged jar. The
    count now comes from the dependency's call-graph sidecar, which keys every analysed unit, pure ones included;
    that is what the in-scan bound counts, and the chained answer now matches the one-tree scan of the same code.
    Executed fixture: 13 overriding subtypes, one writes the file — v0.40.15 reads deny Fs 1 or 0 depending on
    whether the producer names its pure overrides (CANDOR_R1304); this build reads 0 with deny Fs Unknown 1 in
    both. Under the bound (6 subtypes) the union is kept in every arm. zookeeper C_CliCommand: Unknown
    {dispatch} whether chained on the v0.40.14 or v0.40.15 report, so the trade v0.40.15 named is now the rule,
    not a side effect. Cost, chained census, against v0.40.15: 39 concrete deny <E> <unit> cells go 1 → 0 in
    20 consumer units of 19 jars (groovy MetaProperty ×2, jackson JsonSerializer ×2, lettuce CommandOutput
    ×2, log4j Filter/AbstractFilter, bouncycastle ECFieldElement ×4, …); every unit keeps Unknown
    {dispatch}. The licence is the per-row site match: every lost effect came from a v0.40.15 union at a site
    that this build hedges, and the row's dispatch: reason names exactly that owner and member (20/20 rows). That
    every lost cell is also absent from the published one-tree scan is a convergence control (the published engine
    already hedges this shape in-scan), not the licence. The same trade also moves, on the same rows: 26 reason-class
    cells (reflect 14, indirect 11, native 1 — the dropped overrides' classes; each unit keeps Unknown
    {dispatch}), and the dropped overrides' surfaces — incomplete 9 values, netClass 5 (unknown-host), paths
    2 (curator shaded Ordering, groovy MetaProperty ×2, lettuce CommandOutput ×2, jgit RevFilter, scala
    Function0). This supersedes R869's rule that kept the row-priced union beside the hedge (jackson
    StdScalarSerializer.serialize), in every arm. Where the count cannot be answered, see the next entry. The bound
    still adds one for the owner's own body, so an ABSTRACT owner member hedges at 12 declaring subtypes where in-scan
    unions (pre-existing). CANDOR_R1336=0 reads no sidecar, which is the unanswerable state below — it cannot make a
    verdict more resolved than the sidecar does.