|
| 1 | +# CLAUDE.md |
| 2 | + |
| 3 | +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository |
| 4 | +on branches of the 3.x line (`3.1`, `3.2`, `3.x`). |
| 5 | + |
| 6 | +## Project |
| 7 | + |
| 8 | +`jackson-core` is the streaming (incremental) parser/generator layer of Jackson, plus the reference |
| 9 | +JSON implementation of those abstractions. Maven coordinates are `tools.jackson.core:jackson-core` |
| 10 | +and all packages live under `tools.jackson.core` (2.x used `com.fasterxml.jackson.core`) — never |
| 11 | +introduce `com.fasterxml.*` into 3.x code. |
| 12 | + |
| 13 | +Its one compile dependency, `fastdoubleparser`, is shaded and relocated into |
| 14 | +`tools.jackson.core.internal.shaded.fdp` at package time and stripped from the published POM — so |
| 15 | +**consumers see zero external dependencies**. (Unlike 2.x, the relocation path is *not* |
| 16 | +version-suffixed: JPMS module encapsulation already prevents reuse by downstream deps.) |
| 17 | + |
| 18 | +Everything else in Jackson (`jackson-databind`, and every data format backend: Smile, CBOR, XML, |
| 19 | +CSV, YAML, Protobuf...) builds on the abstractions here. That means **public API changes here ripple |
| 20 | +across the whole ecosystem** — treat `JsonParser`, `JsonGenerator`, `TokenStreamFactory`, |
| 21 | +`JsonFactory`, and the `*Feature` enums as frozen in patch releases and additive-only in minor ones. |
| 22 | + |
| 23 | +## Build & test |
| 24 | + |
| 25 | +Use the Maven wrapper (`./mvnw`), not a system `mvn`. |
| 26 | + |
| 27 | +```bash |
| 28 | +./mvnw verify # full build: compile, test, JaCoCo agent + enforcer checks |
| 29 | +./mvnw test # tests only |
| 30 | +./mvnw -B -ff -ntp verify # exactly what CI runs (batch, fail-fast, no transfer log) |
| 31 | + |
| 32 | +./mvnw test -Dtest=UTF8StreamJsonParserTest # single test class |
| 33 | +./mvnw test -Dtest=UTF8StreamJsonParserTest#testFoo # single test method |
| 34 | +./mvnw test -Dtest='*Filtering*' # pattern |
| 35 | + |
| 36 | +./mvnw test jacoco:report # coverage report (separate goal; not part of `verify`) |
| 37 | +./mvnw clean package animal-sniffer:check # verify Android SDK 26 API compatibility |
| 38 | +``` |
| 39 | + |
| 40 | +Note `verify` binds the JaCoCo *agent* and an enforcer rule that requires `jacoco.exec` to exist — |
| 41 | +it does not produce the coverage report. CI generates that in a separate `test jacoco:report` step, |
| 42 | +and runs `animal-sniffer:check` only on the release-build matrix entry. |
| 43 | + |
| 44 | +Baseline is **JDK 17** (source/target); CI builds on 17, 21, and 25 (plus 17 on Windows). Normal |
| 45 | +development works on JDK 17, but *releases* must be built with **JDK 21+** — the enforcer plugin |
| 46 | +fails otherwise, because JDK 17 produces an incomplete `-javadoc.jar` (see #1561 / #1625). |
| 47 | +`animal-sniffer` additionally restricts you to the Android SDK 26 API subset. |
| 48 | + |
| 49 | +Tests are JUnit 5 (`org.junit.jupiter`) with AssertJ available. `src/test/java/perf/` holds manual |
| 50 | +benchmark drivers, not unit tests. |
| 51 | + |
| 52 | +## Branching and release notes |
| 53 | + |
| 54 | +This repo maintains many live branches. Fixes go to the **oldest branch that should receive them**, |
| 55 | +then get merged forward: |
| 56 | + |
| 57 | +``` |
| 58 | +2.21 → 2.22 → 2.x → 3.1 → 3.2 → 3.x |
| 59 | +``` |
| 60 | + |
| 61 | +`2.x` merges into `3.1`, so 2.x fixes flow into the 3.x line. `3.1` is the current patch branch |
| 62 | +(3.1.6-SNAPSHOT), `3.2` the current minor branch (3.2.1-SNAPSHOT), `3.x` the dev branch |
| 63 | +(3.3.0-SNAPSHOT). The `3.0` branch still exists (3.0.5-SNAPSHOT) but is dormant — fully merged into |
| 64 | +`3.1`, no commits since 3.0.4 shipped, and no unreleased section in `release-notes/VERSION`. Start |
| 65 | +from `3.1`, not `3.0`, unless told otherwise. |
| 66 | + |
| 67 | +Don't commit a fix only to `3.x` if it belongs in a patch branch. Propagate with forward merges |
| 68 | +(`git merge 3.1` into `3.2`, then `3.2` into `3.x`) rather than cherry-picks — that is the pattern |
| 69 | +throughout the history. |
| 70 | + |
| 71 | +Every user-visible change gets an entry in `release-notes/VERSION` under the unreleased version, |
| 72 | +formatted as `#<issue>: <summary>` with `(reported by @user)` / `(contributed by @user)` lines, and |
| 73 | +a matching name in `release-notes/CREDITS`. (`VERSION-2.x` / `CREDITS-2.x` are the frozen 2.x |
| 74 | +history; do not add to them.) |
| 75 | + |
| 76 | +## What changed from 2.x (things to unlearn) |
| 77 | + |
| 78 | +- **`JacksonException` extends `RuntimeException`.** Nothing in the streaming API declares |
| 79 | + `throws IOException`; parser/generator methods declare `throws JacksonException`. Low-level I/O |
| 80 | + failures are wrapped as `exc/JacksonIOException`. |
| 81 | +- **Factories are immutable.** There is no `factory.enable(...)` / `factory.configure(...)`. |
| 82 | + Configure through `JsonFactory.builder()` (or `factory.rebuild()` to derive a modified copy). |
| 83 | + `JsonFactory.builderWithJackson2Defaults()` moves defaults back toward 2.x, but its own Javadoc |
| 84 | + says it is a work in progress and does not yet fully replicate them — don't treat it as exact. |
| 85 | +- **`JsonParser.Feature` and `JsonGenerator.Feature` are gone.** Their members were split into |
| 86 | + `StreamReadFeature`/`StreamWriteFeature` (format-neutral) and `JsonReadFeature`/`JsonWriteFeature` |
| 87 | + (JSON-only). `JsonFactory.Feature` moved up to `TokenStreamFactory.Feature`. |
| 88 | +- **Renames**: `JsonLocation` → `TokenStreamLocation`, `JsonStreamContext` → `TokenStreamContext`, |
| 89 | + `JsonGeneratorImpl` → `json/JsonGeneratorBase`, `NonBlockingJsonParser` → |
| 90 | + `json/async/NonBlockingByteArrayJsonParser`. `nextFieldName()` and friends are `nextName()`. |
| 91 | +- **`ObjectCodec` is gone**, replaced by `ObjectReadContext` / `ObjectWriteContext`, which are passed |
| 92 | + explicitly to the `createParser(ObjectReadContext, ...)` / `createGenerator(ObjectWriteContext, ...)` |
| 93 | + overloads. Tree access goes through `TreeCodec` / `TreeNode` (`tree/` holds minimal |
| 94 | + `ArrayTreeNode` / `ObjectTreeNode` impls). |
| 95 | +- **`module-info.java` is a real source file** at `src/main/java/module-info.java` (2.x generated one |
| 96 | + via moditect). Its `module-info.class` lands at the jar root, not under `META-INF/versions/`. |
| 97 | + |
| 98 | +## Architecture |
| 99 | + |
| 100 | +### The layered type hierarchy |
| 101 | + |
| 102 | +Each of parser, generator, and factory has the same shape: **format-neutral abstract API** → |
| 103 | +**shared partial implementation** → **JSON-specific partial implementation** → **concrete class**. |
| 104 | + |
| 105 | +| Layer | Parser | Generator | Factory | |
| 106 | +|---|---|---|---| |
| 107 | +| Neutral API | `JsonParser` | `JsonGenerator` | `TokenStreamFactory` | |
| 108 | +| Partial impl | `base/ParserMinimalBase` → `base/ParserBase` | `base/GeneratorBase` | `base/DecorableTSFactory` → `base/TextualTSFactory` / `base/BinaryTSFactory` | |
| 109 | +| JSON partial | `json/JsonParserBase` | `json/JsonGeneratorBase` | — | |
| 110 | +| JSON impl | `json/ReaderBasedJsonParser`, `json/UTF8StreamJsonParser`, `json/UTF8DataInputJsonParser` | `json/WriterBasedJsonGenerator`, `json/UTF8JsonGenerator` | `json/JsonFactory` | |
| 111 | + |
| 112 | +`base/TextualTSFactory` and `base/BinaryTSFactory` are the intended superclasses for backends in |
| 113 | +other repos (textual formats like YAML/CSV vs binary ones like Smile/CBOR); `JsonFactory` extends |
| 114 | +`TextualTSFactory`. |
| 115 | + |
| 116 | +The non-blocking parsers sit under the same `json/JsonParserBase` node: |
| 117 | +`NonBlockingJsonParserBase` → `NonBlockingUtf8JsonParserBase` → `NonBlockingByteArrayJsonParser` / |
| 118 | +`NonBlockingByteBufferJsonParser`. |
| 119 | + |
| 120 | +Despite the `Json` prefix, only classes in packages containing `json` are JSON-specific. Everything |
| 121 | +else is format-neutral and is subclassed by the binary/text format backends in other repos. |
| 122 | + |
| 123 | +`JsonFactory` decides which concrete parser to build based on the input source: |
| 124 | + |
| 125 | +- `byte[]` / `InputStream` → `ByteSourceJsonBootstrapper` sniffs the encoding. UTF-8 gets the |
| 126 | + dedicated `UTF8StreamJsonParser`; **other encodings are wrapped in a `Reader`** (`UTF32Reader`, or |
| 127 | + an `InputStreamReader`) and handed to `ReaderBasedJsonParser`. |
| 128 | +- `Reader` / `String` / `char[]` → `ReaderBasedJsonParser`. |
| 129 | +- `DataInput` → `UTF8DataInputJsonParser`. |
| 130 | + |
| 131 | +These are largely parallel implementations of the same state machine over different input |
| 132 | +representations — **a bug fixed in one usually needs fixing in all of them**, plus the async parsers. |
| 133 | + |
| 134 | +### Non-blocking parsing |
| 135 | + |
| 136 | +`json/async/NonBlockingByteArrayJsonParser` (and the `ByteBuffer` variant) implement the same token |
| 137 | +stream as a fully resumable state machine driven by `async/ByteArrayFeeder` / `ByteBufferFeeder`. It |
| 138 | +shares no scanning code with `UTF8StreamJsonParser`, so parser fixes typically must be applied here |
| 139 | +separately. `NonBlockingJsonParserBase` holds the shared state/token-id machinery. |
| 140 | + |
| 141 | +### Performance-critical infrastructure |
| 142 | + |
| 143 | +- **`sym/` — symbol tables.** Object property names are canonicalized so repeated names in a document |
| 144 | + become the same `String`. `ByteQuadsCanonicalizer` (UTF-8 byte input) and |
| 145 | + `CharsToNameCanonicalizer` (char input) live as *root* instances on `JsonFactory`; each parser gets |
| 146 | + a **child** via `makeChild()`/`makeChildOrPlaceholder()`, and `release()` merges learned names back |
| 147 | + into the root (via `mergeChild()`) when the parser closes. This is where hash-collision DoS |
| 148 | + protection lives. Note: `INTERN_PROPERTY_NAMES` is **disabled** by default in 3.x (it was on in 2.x); |
| 149 | + `CANONICALIZE_PROPERTY_NAMES` remains on. |
| 150 | +- **`util/BufferRecycler`, `util/RecyclerPool`, `util/JsonRecyclerPools`.** Parsers and generators |
| 151 | + lease their I/O and text buffers from a pooled `BufferRecycler`, held via `io/IOContext`. Pool |
| 152 | + strategy is pluggable per-factory. Failing to release a buffer on an error path is a real leak. |
| 153 | +- **`util/TextBuffer`.** Grow-on-demand character accumulator used for decoded string values; |
| 154 | + `ReadConstrainedTextBuffer` is the variant that enforces `maxStringLength`. |
| 155 | +- **`io/NumberInput`, `io/BigDecimalParser`, `io/BigIntegerParser`, `io/schubfach/`.** Number |
| 156 | + decoding/encoding, delegating to shaded `fastdoubleparser` for `double`/`float` and to Schubfach |
| 157 | + for shortest-repr output. |
| 158 | + |
| 159 | +### Configuration model |
| 160 | + |
| 161 | +Feature *state* is held as plain `int` bitmask fields on `TokenStreamFactory` — `_factoryFeatures`, |
| 162 | +`_streamReadFeatures`, `_streamWriteFeatures`, `_formatReadFeatures`, `_formatWriteFeatures` — all |
| 163 | +`final`. `util/JacksonFeatureSet` is a separate, immutable holder used for the **capability** enums, |
| 164 | +reached via `streamReadCapabilities()` / `streamWriteCapabilities()`. |
| 165 | + |
| 166 | +There are several distinct axes, and putting a feature on the wrong one is an API mistake that can't |
| 167 | +be undone: |
| 168 | + |
| 169 | +- `TokenStreamFactory.Feature` — factory-level, affecting how parsers/generators get constructed |
| 170 | + (symbol table interning, canonicalization, hash-collision handling). Implements `JacksonFeature`. |
| 171 | +- `StreamReadFeature` / `StreamWriteFeature` — format-neutral, per parser/generator. Implement |
| 172 | + `JacksonFeature`. |
| 173 | +- `JsonReadFeature` / `JsonWriteFeature` — JSON-only. Unlike 2.x, these are first-class: |
| 174 | + they implement `FormatFeature` and own the `_formatReadFeatures` / `_formatWriteFeatures` bitmasks |
| 175 | + rather than mapping onto legacy `JsonParser.Feature` constants. |
| 176 | +- `StreamReadCapability` / `StreamWriteCapability` — what a backend *can* do, not what it's told to do. |
| 177 | + |
| 178 | +All configuration flows through `TSFBuilder` / `JsonFactoryBuilder`. There is no mutable-setter |
| 179 | +fallback. |
| 180 | + |
| 181 | +### Processing limits (security-relevant) |
| 182 | + |
| 183 | +Three per-factory config objects, with quite different scopes — don't conflate them: |
| 184 | + |
| 185 | +- **`StreamReadConstraints`** does the heavy lifting. `validateNestingDepth`, `validateDocumentLength`, |
| 186 | + `validateTokenCount`, `validateFPLength`, `validateIntegerLength`, `validateStringLength` |
| 187 | + (plus `validateStringLengthLong`), `validateNameLength`, `validateBigIntegerScale`. |
| 188 | +- **`StreamWriteConstraints`** bounds exactly one thing: output nesting depth. |
| 189 | +- **`ErrorReportConfiguration`** is *not* an input limit — it caps how much content |
| 190 | + (`maxErrorTokenLength`, `maxRawContentLength`) gets embedded in exception messages. |
| 191 | + |
| 192 | +Constraint violations throw `exc/StreamConstraintsException`. This module is continuously fuzzed by |
| 193 | +OSS-Fuzz; `src/test/java/tools/jackson/core/unittest/fuzz/` and `.../dos/` hold regression tests from |
| 194 | +those findings, and `.../constraints/` tests the limits themselves. New parsing code paths that can |
| 195 | +accumulate unbounded input must consult the relevant constraint. |
| 196 | + |
| 197 | +### Filtering |
| 198 | + |
| 199 | +`filter/FilteringParserDelegate` and `filter/FilteringGeneratorDelegate` wrap a parser/generator and |
| 200 | +apply a `TokenFilter` (commonly `JsonPointerBasedFilter`) to include/exclude subtrees while keeping |
| 201 | +the surrounding token stream well-formed. `TokenFilterContext` tracks the deferred "has the parent |
| 202 | +START_OBJECT been emitted yet?" bookkeeping that makes this work. |
| 203 | + |
| 204 | +## Code conventions |
| 205 | + |
| 206 | +- Non-public instance/static fields are prefixed with `_` (`_currToken`, `_inputBuffer`). Public API |
| 207 | + never exposes fields. |
| 208 | +- Every new public method/class carries an `@since 3.NN` Javadoc tag for the version it lands in. |
| 209 | +- Long-lived comments are dated and attributed — `// 11-May-2020, tatu: ...` — with a |
| 210 | + `[core#1264]` issue reference when one applies. Follow this format when leaving a non-obvious note. |
| 211 | +- Tests for a specific GitHub issue are named after it: `GeneratorFiltering890Test`, |
| 212 | + `Base64Padding912Test`, `Fuzz34435ParseTest`. |
| 213 | +- Tests live under `src/test/java/tools/jackson/core/unittest/`, grouped by feature area (`base64/`, |
| 214 | + `filter/`, `json/`, `sym/`...), not by read/write side. |
| 215 | +- Most tests extend `JacksonCoreTestBase`, which supplies the `MODE_*` constants |
| 216 | + (`MODE_INPUT_STREAM`, `MODE_READER`, `MODE_DATA_INPUT`, throttled variants...). Parser tests should |
| 217 | + loop over `ALL_MODES` (or a relevant subset like `ALL_BINARY_MODES` / `ALL_TEXT_MODES`) so every |
| 218 | + input backend gets exercised — this is how the parallel-implementation problem above is caught. |
| 219 | + Async tests use `AsyncTestBase` (`asyncForBytes(factory, bytesPerRead, data, padding)`). |
| 220 | +- `src/test/.../unittest/testutil/` holds fakes (`ThrottledInputStream`, `ThrottledReader`, |
| 221 | + `MockDataInput`) that force buffer-boundary splits; use them when a fix concerns content spanning a |
| 222 | + buffer edge. |
| 223 | +- A known-broken test goes in the `unittest/tofix` package annotated `@JacksonTestFailureExpected`, |
| 224 | + which *fails the build if the test starts passing*. Use it to lock in a reproduction before the fix |
| 225 | + exists; remove the annotation and move the test when fixing. |
| 226 | + |
| 227 | +## Things that are easy to get wrong |
| 228 | + |
| 229 | +- **New public package** → must be added by hand to `src/main/java/module-info.java` (and the test |
| 230 | + counterpart at `src/test/java/module-info.java` if tests need it). The `osgi.export` property in |
| 231 | + `pom.xml` uses a `tools.jackson.core.*` wildcard and needs no change, but the module-info lists |
| 232 | + every package explicitly, so a missing `exports` fails silently at build time and loudly for users. |
| 233 | +- **Shading**: `dependency-reduced-pom.xml` at the repo root is generated by the shade plugin; don't |
| 234 | + hand-edit it. |
| 235 | +- **Java `\u` in comments and strings**: javac processes `\uXXXX` escapes *before* lexing, even inside |
| 236 | + comments. Write `\\u`, or avoid it (e.g. `%04X` in a `String.format`). |
| 237 | +- `pom.xml` pins `project.build.outputTimestamp` for reproducible builds. |
| 238 | + |
| 239 | +## Misc |
| 240 | + |
| 241 | +- Always ask for permission for "git commit" and "git push". |
| 242 | +- Make commit messages, issue titles compact; avoid unnecessary verbosity. |
| 243 | +- Same with comments: use accurate, concise descriptions. |
0 commit comments