Skip to content

Consolidate the hardening internals per JAXP type - #28

Open
ppkarwasz wants to merge 6 commits into
mainfrom
feature/reduce-shade-footprint
Open

Consolidate the hardening internals per JAXP type#28
ppkarwasz wants to merge 6 commits into
mainfrom
feature/reduce-shade-footprint

Conversation

@ppkarwasz

Copy link
Copy Markdown
Member

Relationship to COMMONSXML-3

This PR is adjacent to COMMONSXML-3 (Reduce shading footprint), but deliberately stops short of it: it does not add alternative public entry points. It is too soon to commit to a per-hardener API, so XmlFactories remains the only public class and its API is unchanged.

What it does instead is consolidate methods by moving each shared helper to its only caller, so that the internals no longer form one tangle in which every class transitively reaches every other. Each JAXP type (DOM, SAX, StAX, TrAX, XPath, schema) now has a self-contained hardener whose transitive class closure contains only what that type actually uses. If COMMONSXML-3 is taken up later, exposing an entry point becomes a visibility change instead of a refactoring.

Changes

One commit per step, each behavior-preserving:

  1. Centralize hardening messages on HardeningException. Two static helpers, settingFailed(...) and forbidden(...), replace the message construction that previously coupled the setters and the resolver floors to their container classes. Messages are byte-identical.
  2. Inline the typed JAXP setters into each hardener. The setFeature/setOptionalFeature/trySetProperty helpers move out of the shared JaxpSetters class into the hardener that uses them, as private methods; JaxpSetters is deleted. Shading one hardener no longer drags in the setter overloads of the others.
  3. Split the resolver floors into top-level classes. The five nested Resolvers.Fallback* floors become top-level, package-private classes (FallbackDenyEntityResolver2, FallbackDenyXMLResolver, FallbackIgnoreXMLResolver, FallbackDenyURIResolver, FallbackDenyLSResourceResolver) and the Resolvers container is deleted. Nested classes are kept together by minimizeJar; top-level classes are pulled in per floor.
  4. Break the XmlFactories cycle for TrAX, XPath and schema. The wrappers re-hardened their sub-parsers by calling back into XmlFactories.harden(Source/XMLReader), and since XmlFactories news up every hardener, that one call dragged the whole library into their closures. The bodies move to package-private SAXParserHardener.hardenSource/hardenReader; the public XmlFactories.harden methods stay as thin delegates.
  5. Add SchemaHardener as the schema entry point. Package-private like the other hardeners; it gives schema validation the same shape as the other types (XmlFactories delegates to a dedicated hardener) and would back a public entry point later.

Effect on the shade closures

Before this PR the TrAX, XPath and schema entry points transitively reached XmlFactories and therefore pulled the whole library (32 classes each), while the DOM, SAX and StAX hardeners pulled the shared setter class and all five resolver floors whether they used them or not. After:

Entry point Classes in closure
DocumentBuilderHardener 5
StaxHardener 6
SAXParserHardener 8
XPathHardener 13
SchemaHardener 14
TransformerHardener 17
XmlFactories (public) 33 (whole library)

TrAX, XPath and schema build on the shared SAX closure (8 classes), since they re-harden their sub-parsers through it.

The new ShadingFootprintTest pins each closure with jdependency (the library maven-shade-plugin's minimizeJar uses), so a hardener silently regaining a dependency on a sibling floor or another hardener fails the build. The test is JVM-only and excluded from the Android test compile.

Testing

Full surefire matrix (stock JDK, Xerces, Xalan, Xalan+Xerces, Saxon, Saxon+Xerces, Woodstox): all green. No public API change, no behavior change; exception messages are byte-identical.

🤖 Generated with Claude Code

Add two static helpers to HardeningException so every hardener and every
resolver floor shares one message format:

- settingFailed(kind, name, target, cause) replaces JaxpSetters' inline
  "Failed to set ..." construction.
- forbidden(...) replaces the private Resolvers.forbiddenMessage, so the
  five floors no longer route their message through the outer Resolvers
  class.

Behavior-preserving: the messages are byte-identical. This is the shared
core for the per-hardener shade-footprint reduction that follows.

Add ShadingFootprintTest, which uses jdependency (the library maven-shade
minimizeJar uses) to pin each hardener entry point's transitive class
closure and print its size as a share of the full library, so later
phases can show the footprint shrinking.

Assisted-By: Claude Opus 4.8 <noreply@anthropic.com>
Move the setFeature/setOptionalFeature/trySetProperty/trySetAttribute/
setOptionalAttribute helpers out of the shared JaxpSetters class and into
the hardener that uses them, as private methods, so shading one hardener
no longer drags in the setters (and dead overloads) of the others. Only
the shared "Failed to set ..." message stays central, on
HardeningException.settingFailed. Delete JaxpSetters.

Cuts the DOM/SAX/StAX shade closures by the whole JaxpSetters class
(7946 bytes): DocumentBuilderHardener 26225 -> 19265, SAXParserHardener
32508 -> 25721, StaxHardener 28173 -> 20547 bytes. Update
ShadingFootprintTest's expected sets accordingly.

Assisted-By: Claude Opus 4.8 <noreply@anthropic.com>
Promote the five nested Resolvers.Fallback* floors to top-level,
package-private classes and delete the Resolvers container. The floors
were bound together only by the shared forbiddenMessage, now on
HardeningException (Phase 1), and by being nested in one class, which
maven-shade minimizeJar keeps together. Splitting them means shading one
hardener pulls only the floor(s) it uses:

- DocumentBuilderHardener: 10 -> 5 classes, 19265 -> 12161 bytes
- SAXParserHardener:       13 -> 8 classes, 25721 -> 18242 bytes
- StaxHardener:            10 -> 6 classes, 20547 -> 13433 bytes

The EntityResolver2 floor is named FallbackDenyEntityResolver2; it also
carries the shared "floor" overview the container used to hold. Update
ShadingFootprintTest's expected sets and exclude that JVM-only test
(jdependency) from the Android test compile.

Assisted-By: Claude Opus 4.8 <noreply@anthropic.com>
The TrAX, schema and Saxon wrappers re-harden their sub-parsers by
calling XmlFactories.harden(Source/XMLReader); because those helpers live
on XmlFactories, whose class also news up every hardener, referencing
them dragged the whole library into each of those shade closures.

Move harden(Source)'s body to package-private SAXParserHardener.
hardenSource (it needs only the SAX path) and repoint the internal
callers: the four Source callers to hardenSource, SaxonProvider to
SAXParserHardener.hardenReader. The public XmlFactories.harden(Source)/
harden(XMLReader) stay as thin delegates, so the API is unchanged, but no
hardener transitively reaches XmlFactories anymore.

Each heavy entry point now pulls only its own wrappers, its floor and the
shared SAX path:

- TransformerHardener:    32 -> 17 classes, 75204 -> 37366 bytes
- XPathHardener:          32 -> 13 classes, 75204 -> 26471 bytes
- HardeningSchemaFactory: 32 -> 13 classes, 75204 -> 32763 bytes

Pin the three closures in ShadingFootprintTest and assert only the public
XmlFactories entry still pulls all 32 classes.

Assisted-By: Claude Opus 4.8 <noreply@anthropic.com>
Give schema validation the same shape as the other JAXP types: a
dedicated hardener class that XmlFactories delegates to, instead of
newSchemaFactory wrapping HardeningSchemaFactory directly. The class
stays package-private like the other hardeners; it will back a public
entry point if the hardeners are exposed later.

Pin SchemaHardener as the schema entry point in ShadingFootprintTest.

Assisted-By: Claude Fable 5 <noreply@anthropic.com>
The test reads the compiled .class files from the code-source location,
which only exists on a regular JVM: a native image carries no bytecode
(and nobody shades one), so every closure lookup fails there. Disable it
with @DisabledInNativeImage, matching its exclusion from the Android
test compile.

Assisted-By: Claude Fable 5 <noreply@anthropic.com>
@garydgregory
garydgregory force-pushed the feature/reduce-shade-footprint branch from cd67911 to 0fcd0a1 Compare August 2, 2026 19:16
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant