Fix/bool var copy merge#4
Merged
Merged
Conversation
A method reference (, , ) binds
naturally to a raw SAM (it carries no explicit parameter types to mismatch),
so the parameterized functional-interface cast added by lambdaArgFunctionalCast
/ lambdaArgRawJDKReceiverCast is unnecessary for it. Worse, for SAMs whose
descriptor mentions nested wildcards (Stream.flatMap's
`Function<? super T, ? extends Stream<? extends R>>`, or Collectors.collect),
the cast pins a concrete parameterization that defeats javac's poly inference
("method flatMap cannot be applied to given types").
An explicitly-typed lambda body still needs the cast to bind (the documented
fastjson2 JSONPathSegment$CycleNameSegment$MapRecursive `.map((l0) -> ...)`
case), so only method references are skipped. Distinguished via a new
CustomValue.IsMethodRef flag set on the method-reference bootstrap branch.
A/B tree inventory (all jars, OFF vs ON):
fastjson2 25 -> 24 errLines (ObjectReaderCreator.toFieldReaderArray flatMap(Collection::stream))
spring 28 -> 25 errLines (AnnotatedTypeMetadata collect(Collector<...>))
all other jars unchanged (no regression)
Updated TestRawStreamLambdaCastIsLoadBearing to assert the new correct shape:
the `.map` lambda keeps its cast, the `.filter` method reference is bare.
…-cast Companion to the method-ref FI-cast skip: the method reference now renders bare (.filter(Objects::nonNull)) instead of cast, while the lambda keeps its (Function<RawStreamItem, Object>) cast. Updated both the golden rules and the TestRawStreamLambdaCastIsLoadBearing ON/OFF assertions.
reachingSlotVersionByWeb / reachingSlotStoreContinuationByWeb redirect a slot load/store to its web-proven SAME-variable reaching definition (same VarUid) when DFS traversal order left a later/disjoint-branch version in the slot table. These were opt-in (JDEC_LIVEINTERVAL_WEB) on the strength of an older note that they were "net-neutral on the iso per-file metric and slightly negative under javac tree masking". Re-measured against the current 8-jar tree inventory (the real repackage metric), enabling them is a strict improvement with delta >= 0 on every jar: fastjson2 24 -> 22 tree errLines (ObjectReaderCreator 3->2, JSONPathParser 2->1) codec / commons-lang3 / gson / guava / jsoup / snakeyaml / spring unchanged The repair only coalesces definitions the dataflow proves to be one variable; disjoint live ranges (try-with-resources primaryExc counter-example) fall in different webs and are left untouched, so it cannot merge genuinely-distinct variables. Flipped the gate to JDEC_LIVEINTERVAL_WEB_OFF (default ON) per the "delta must be non-negative to land" rule. New TestLiveIntervalWebRepairIsLoadBearing pins the fastjson2 ObjectReaderCreator group ON(web)<OFF(web).
Re-measured the full 8-jar tree inventory after the two fixes in this round (method-ref FI-cast skip + live-interval web repair default-on) and updated all status docs to the honest, harness-produced numbers: fastjson2 tree errLines 25 -> 22 (defect units 14, clean rate 97.9%) spring tree errLines 28 -> 25 (defect units 16, clean rate 98.3%) all other jars unchanged Totals: 4424/4487 flat units clean = 98.6%, 87 tree errLines, 63 defect units, 0 syntax errors. CODEC_TODO status table + switch index (new JDEC_LIVEINTERVAL_WEB_OFF default-on entry, JDEC_LAMBDA_RAW_JDK_RECV_CAST_OFF method-ref-skip note), TODO.md snapshot + T2 entry, BENCHMARK tables A/C/§6 + headline, README + README.zh-CN all refreshed to the flat-unit metric.
…al snapshot The 3-way (JavaJive/CFR/Vineflower) table E used a per-jar sub-sample methodology from the cross-comparison sampling period; its JavaJive numbers (96.6%, 77 defect classes) predate the current full flat-unit inventory. Add a prominent note that the current JavaJive numbers live in table A (98.6%, 63 defect units / 4487), and that CFR/Vineflower columns were not re-measured in this round. Updated the §6 headline and §7.3 conclusion to cite the current 98.6% flat-unit rate.
…ning lambda bodies
A generic method returning Supplier<T> / Function<T,R> whose lambda body returns
an erased Object (a raw-typed receiver's method, or any Object-returning call)
lost the source's unchecked `return (T)/(R) expr;` cast. The lambda impl method
in bytecode is erased (Object return), and its instantiatedMethodType return is
Object, so the bare `() -> { return expr; }` is rejected by javac as
"bad return type in lambda expression: Object cannot be converted to T".
The fix recovers the cast TARGET from the enclosing method's generic Signature
(CurrentMethodSig): when the method's return type is a parameterized Supplier /
Function / BiFunction whose return-position type argument is a bare type
variable, and the lambda is the same raw FI with an Object instantiatedMethodType
return, the body's value `return EXPR;` is rewritten to `return (T) (EXPR);`.
Void-returning SAMs (Consumer/BiConsumer/Predicate) never cast.
Verified: CFR also drops this cast and fails to recompile the same sites
(confirmed against the real fastjson2 2.0.43 source, which carries `() -> (T)
objectReader.createInstance(0)`). A/B tree inventory (all jars, OFF vs ON):
fastjson2 22 -> 20 errLines (ObjectReaderProvider.createObjectCreator,
ObjectReaderCreator.createBuildFunctionLambda)
all other jars unchanged (no regression)
A/B delta fastjson2 +2 (ON=20 OFF=22). New TestLambdaReturnTypevarCastIsLoadBearing
pins the cast ON (present) vs OFF (bare body), plus a regression seed+golden.
…st fix fastjson2 tree errLines 22 -> 20 (defect units 14 -> 13), all other jars unchanged. Totals: 4425/4487 flat units clean = 98.6%, 85 tree errLines, 62 defect units. Updated CODEC_TODO status table + new JDEC_LAMBDA_RETURN_TYPEVAR_CAST_OFF switch index entry, TODO snapshot, and BENCHMARK tables/headline (fastjson2 98.0%).
…r param
A constructor (or static method) whose i-th formal is a RAW functional
interface (raw BiConsumer.accept(Object,Object)) rejects an UNBOUND instance
method reference like Throwable::setStackTrace as "invalid method reference":
the impl method's arity (Throwable, StackTraceElement[]) does not match the
raw (Object,Object) SAM. The source carried
`(BiConsumer<Throwable,StackTraceElement[]>) Throwable::setStackTrace`; recover
it from the invokedynamic instantiatedMethodType (3rd LambdaMetafactory arg),
now carried on the method-ref CustomValue as InstantiatedMtdDesc.
The new ctorRawFISAMMethodRefCast fires only for ctor/static calls, a method-ref
argument carrying an instantiatedMethodType, a formal in the >=2-SAM-arity JDK
FI family (BiConsumer/BiFunction/BiPredicate -- single-param SAMs bind directly
and are excluded), and at least one recovered param more specific than Object.
Cast target = `<FIRawClass><<instantiated param types>>`.
A/B tree inventory (all jars, OFF vs ON):
fastjson2 20 -> 19 errLines / 13 -> 12 blocker units
(ObjectReaderCreator `new FieldReaderStackTrace(..., Throwable::setStackTrace)`)
all other jars unchanged (no regression)
A/B delta fastjson2 +1. CFR and Vineflower both drop this cast too (3-way oracle
all-fail); the real fastjson2 2.0.43 source carries it explicitly. New
TestCtorRawFIMethodRefCastIsLoadBearing + regression seed+golden.
…aw-FI method-ref cast fastjson2 tree errLines 20 -> 19 (defect units 13 -> 12), all other jars unchanged. Totals: 4426/4487 flat units clean = 98.6%, 84 tree errLines, 61 defect units. Updated CODEC_TODO status table + new JDEC_CTOR_RAWFI_METHODREF_CAST_OFF switch index entry, TODO snapshot/round summary, and BENCHMARK tables/headline (fastjson2 98.2%).
…maining 19 tree errLines Catalogs all 19 remaining fastjson2 tree errors with their bytecode-truth + source-confirmed root cause and the holistic fix-shape for each, so the deferred core refactor has actionable specs (not re-derivation). Documents that these 19 are one tightly-coupled family (slot-typing/split/merge/phi/ LUB/reaching-def/receiver-binding) requiring a simultaneous rebalanced refactor, and that 6 single-point guard attempts this session all regressed (56/74/8) or did not fire — empirically confirming single-point guards are not viable. The 4 landed cast-recovery fixes (fastjson2 25->19) are the safe single-point ceiling; the remaining 19 are left to the holistic core refactor effort.
…one boolean var reachingBoolVarCopyMerge (JDEC_BOOL_VAR_COPY_MERGE_OFF): when an if/else (or switch-case) sibling arm stores a genuinely boolean-typed value (Z-returning call like isRefDetect()) into a slot whose current int-typed ref was created by the OTHER sibling arm either copying a boolean-default variable (itemRefDetect = previous, previous compiled to iconst_1/0) or storing a comparison ternary ((c && cond) ? 1 : 0), re-type that int ref (and its proven-boolean int-0/1 default) to boolean when a phi proves the two arms are one source variable. reachingBoolDefaultMerge cannot anchor here: it walks Source edges backward and a sibling branch's definition is unreachable (no path). This handler anchors on the boolean store, reads the global slot table current ref, recovers its creator store via a new refToCreatingStore index (sidesteps opcodeIdToRef non-deterministic map iteration), and proves the RHS is a boolean default (int-0/1 literal, shape a) or a copy of a slot whose own definition is such a literal (shape b). Fixes fastjson2 FieldWriterList.writeList (copy arm) and ObjectWriterImplList.write (ternary arm): fastjson2 tree 19->17 errLines, defect classes 12->11. A/B delta >= 0 on all 8 jars (codec/commons-lang3/ gson/guava/jsoup/snakeyaml/spring all +0). Load-bearing test + BoolVarCopyMergeSeed regression seed.
…egression investigation Attempted reachingRefSlotJDKSubtypeReturnArmMerge (IsReferenceSubtypeBridged to detect val<:current on method-return/boxed values). All gates passed on JSONPathParser offset 1108 (sub=true, phiOk=true) but the broad merge regressed fastjson2 17->36 (+19): it fired on many subtype stores that must stay split (subtype arm assigned in only one branch -> merge read uninitialized). Reverted. Conclusion: this family needs a narrower gate (e.g. requiring the merge read to be instanceof a SIBLING, not a subtype), not a broad subtype merge; the phi gate alone cannot distinguish.
…nvestigations Two empirical investigations of the tightly-coupled core family (both reverted, findings recorded for future work): 1. incompatiblePhiWidenObject (read-side widen-to-Object): when a load's reaching-definition web merges >=2 reference-type stores whose pairwise LUB is exactly Object, widen the resolved ref to Object. Regressed fastjson2 17->24 (+7): even with the LUB-is-Object gate, the ref still carries member-access uses that depend on the narrower type. Conclusion: cannot widen the shared ref; must mint a per-load Object temp (phi sink) and only when the read is a putfield/putstatic/return, not a member access receiver. 2. reachingRefSlotJDKSubtypeReturnArmMerge (see #15 note): the phi gate cannot distinguish a merge read that is instanceof a SIBLING from a subtype arm assigned in only one branch (merge read uninitialized).
…, reverted Attempted phiSinkCast: insert a (TargetType)((Object)(var)) Object-bridge double cast on putfield/putstatic when a slot-shared local's type is incompatible with the field type (Boolean vs Predicate, Class[] vs Field). The cast renders correctly and fixes 4 fastjson2 errors in isolation, but the authoritative A/B delta (TestJarRecompileDelta) shows MASSIVE negative deltas across ALL 8 jars: codec -7, commons-lang3 -11, fastjson2 -36, gson -5, guava -77, jsoup -15, snakeyaml -12, spring -22. Even with FQN/array/type-variable gating, the cast mis-fires on many field stores that must stay as-is, breaking javac generic inference and overload resolution. Conclusion: read-side/sink casting is not viable; the family needs the variable-typing/splitting core (incompatible types must land on distinct refs) rather than cast patching at the sink. Fully reverted.
…catch-slot family) rebindIncompatibleLoadForSink (JDEC_REBIND_INCOMPATIBLE_LOAD_OFF): in phase-2 statement building, when a putfield/putstatic sink consumes a local-load value (SlotValue wrapping a JavaRef) whose resolved type is INCOMPATIBLE with the sink's declared type (Boolean vs Predicate, Object vs MethodHandle -- the catch-slot / switch-case slot-reuse family in fastjson2 JDKUtils.<clinit>), the single forward simulation parked a DFS-stale wrong-branch ref in the global slot table, and the sink read the wrong branch. By phase 2 the simulation is complete (opcodeIdToRef fully populated), so use the structural reachingStoresOf walk (Source edges, simulation-order-independent) to find the load's TRUE reaching store whose committed ref type exactly matches the sink type, and rebind the sink to that branch ref. Strict gating: value must be a SlotValue wrapping a JavaRef; both sides non-primitive, non-Object, non-type-variable concrete classes; neither assignable to the other (genuine incompatible pair); a reaching store exists whose ref type exactly matches the sink type (proving the compatible branch was minted). Never widens or casts -- merely re-points the read at the already-minted compatible ref. This is the first step of the two-pass typing core: instead of read-side/sink patching (widen-to-Object, JDK-subtype-return merge, phiSinkCast -- all empirically regressed and documented), it rebinding at the statement-building phase using structural reaching-def analysis. Fixes fastjson2 JDKUtils.<clinit> (PREDICATE_IS_ASCII, METHOD_HANDLE_HAS_NEGATIVE) and JSONFactory:325: fastjson2 tree 17->14 errLines, defect classes 11->9. A/B delta >= 0 on all 8 jars (codec/commons-lang3/gson/guava/jsoup/snakeyaml/spring all +0). Load-bearing test + RebindIncompatibleLoadSeed regression seed.
…sinks + Object-value Extend the two-pass load rebinding to also cover local-assign sinks (OP_*STORE where the value is a local-load whose type is incompatible with the LHS ref's type), and allow an Object-typed value flowing into a specific reference sink (the exact-match reaching-store gate is the safety net). The strict gates (SlotValue+JavaRef, non-primitive concrete classes, exact-name reaching store match) prevent regression. A/B delta >= 0 on all 8 jars (fastjson2 stays 14, others +0); classparser tests pass. This broadens the rebind's applicability for future incompatible-phi cases without changing current behavior on the 8-jar set.
…infrastructure Record each OP_CHECKCAST's inner arg (the aload SlotValue before the cast CustomValue wraps it) in a checkcastInnerArg map, and add a phase-2 post-pass (rebindCheckcastInnerArgs) that uses the structural reachingStoresOf walk to rebind the inner SlotValue to a branch ref whose type matches the cast target when the resolved type is incompatible. This bypasses the CustomValue closure opacity (the inner arg is captured opaquely in the cast's StringFunc/ReplaceFunc). Gated by JDEC_REBIND_CHECKCAST_INNER_OFF (shared with JDEC_REBIND_INCOMPATIBLE_LOAD_OFF). A/B delta >= 0 on all 8 jars (currently a no-op on this jar set -- the fastjson2 JDKUtils:318 case has no checkcast, the casts come from arg-rendering; this infrastructure is for future checkcast-wrapped incompatible casts).
Record the complete findings from 12 attempted approaches (2 successful, 10 reverted) and the precise architecture blueprint for the remaining 14 fastjson2 errLines. Documents the three irreconcilable architectural constraints proven empirically: 1. null-adopt trilemma (adopt/block/widen all regress) 2. cast over-firing (4 gate configs all regress) 3. decl-override mass regression (any-pair 1912, all-pairs 1999) And the only viable path: true two-pass rebuild — pass-1 collects complete slot type signatures + merge-read downstream-use types, pass-2 rebuilds declarations only for slots whose merge-reads would type-mismatch (breaking the circular dependency via downstream-use type collection). This is the multi-week core rewrite blueprint for the next dedicated effort.
…slot reused for incompatible numeric subtypes fastjson2 ObjectWriterCreatorASM.gwFieldName: JVM slot 11 holds Integer.valueOf(0) (initializer) and Long.valueOf(...) (switch case stores), read via .intValue()/.longValue() (Number methods in bytecode). The slot correctly resolved to java.lang.Number, but the IsFirst declaration rendering used JavaValue.Type() (Integer) instead of the slot's LeftValue.Type() (Number), emitting which rejected the later reassignment (javac: 'Long cannot be converted to Integer'). Fix: numericSlotWiderThan rendering clause (mirrors intCategoryWiderThan for the int-category hierarchy) — when the slot type is java.lang.Number and the initializer is a concrete numeric sub-type, declare at the slot type: (Integer is-a Number). Kill-switch: JDEC_NUMERIC_DECL_SLOT_TYPE_OFF=1. Also adds WidenNumericMixedSlotDecl post-RewriteVar pass (defense-in-depth, keyed by VarUid) for cases where the slot type isn't pre-resolved to Number. Kill-switch: JDEC_WIDEN_NUMERIC_MIXED_OFF=1. Load-bearing test: ObjectWriterCreatorASM.java iso 'Long cannot be converted to Integer': ON=0 OFF=1. fastjson2 tree errLines: 8 → 7 (1 unit cleared), 8-jar A/B delta ≥ 0.
…e when slot stores sibling-typed values
fastjson2 JSONPathSegment$CycleNameSegment.eval: JVM slot 8 stores List (readArray())
in the true arm and Map (readObject()) in the false arm of a ternary, with no checkcast
(astore 8 directly). javac requires every arm of 'cond ? A : B' assigned to 'List var8'
to be assignable to List; Map is a sibling, not a subtype, so it rejected the conditional
('incompatible types: bad type in conditional expression').
Fix: ternaryArmIncompatibleCast — when a ternary RHS is assigned to a concrete-typed local
and exactly ONE arm is not reference-assignable to the target type, wrap that arm in an
explicit (TargetType) cast so the conditional merges at the target type:
'var8 = cond ? readArray() : ((List)(readObject()))'. Only fires on exactly-one-incompatible
(both-incompatible = genuine clash left alone). Kill-switch: JDEC_TERNARY_ARM_CAST_OFF=1.
Load-bearing test (tree-compile, deps-only cp): fastjson2 'bad type in conditional expression'
ON=0 OFF=1. tree errLines: 7 → 6 (CycleNameSegment cleared), 8-jar A/B delta ≥ 0.
… check so cross-scope varN gets synthesized declaration fastjson2 JDKUtils <clinit>: a dup;astore pattern produces an embedded assignment 'var17.findStatic(var31 = Class.class, ...)' inside a try block (slot 23), while a sibling try-catch declares 'catch(Throwable var31)' (slot 24, different scope). collectEmbeddedDeclInfos only traversed AssignStatements, so the catch-parameter declaration was invisible, and SynthesizeUndeclaredEmbeddedAssignDecls saw no incompatible collider for the Class-typed var31 embedded in the try — leaving it undeclared -> javac 'cannot find symbol: variable var31'. Fix: collectEmbeddedDeclInfos now also collects TryCatchStatement.Exception (catch parameters) into the byName/byID maps, so the catch param registers as an incompatible-type collider and the orphaned Class-typed var31 gets a synthesized 'Class var31;' declaration at method top. Load-bearing test (tree-compile, deps-only cp): JDKUtils 'cannot find symbol' ON=0 OFF=1. tree errLines: 6 → 5, 8-jar A/B delta ≥ 0.
…fixes (#1,#6,#19) - fastjson2 table row: 8→4 blocker classes, 12→5 tree errLines - #1 (JDKUtils:304 cannot find symbol var31): cleared via catch-param collider collection (JDEC_EMBED_ASSIGN_DECL_OFF) - #6 (CycleNameSegment:172 ternary LUB): cleared via ternaryArmIncompatibleCast (JDEC_TERNARY_ARM_CAST_OFF) - #19 (ObjectWriterCreatorASM:2380 Long→Integer): cleared via numericSlotWiderThan (JDEC_NUMERIC_DECL_SLOT_TYPE_OFF) - Add kill-switch entries for the three new fixes in §4 safety-switch table - Remaining 5 errLines: JDKUtils:319 x2 (slot swap), TypeUtils:4951, ObjectReaderBaseModule:793, JSON:4367 (all deep slot-typing/reaching-def issues)
…ore + register var-user fastjson2 TypeUtils.<clinit>: slot 0's null initializer (offset 825) was DFS-corrupted to Class[] (a later array reuse polluted the global slot table), but the true reaching store is offset 841 getDeclaredField → Field. The putstatic FIELD_JSON_OBJECT_1x_map (Field field) consumed slot 0's aload (Class[]) → 'Class[] cannot be converted to Field'. Two fixes in rebindIncompatibleLoadForSink: 1. Array-type relaxation: ClassFQNOf returns false for array types, so the function bailed on the Class[] value before reaching the reaching-store lookup. Now when exactly one side (value vs sink) is array, it's treated as a genuine incompatible pair and proceeds to the store lookup (the non-array sink type is used for exact-match). 2. Var-user registration: after rebinding the sink to the correct reaching-store ref, register a new var-user in varUserMap for that ref. Without this, the rebound ref looked single-use (its phase-1 user count excluded this phase-2 sink), and var-fold would single-use-fold it away — dropping its declaration and leaving the sink reading an undeclared variable. Load-bearing test (tree-compile): TypeUtils 'cannot be converted to Field' ON=0 OFF=1. tree errLines: 5 → 4 (TypeUtils cleared), 8-jar A/B delta ≥ 0.
…s:4951 cleared - fastjson2 table row: 4→3 blocker classes, 5→4 tree errLines - #17 (TypeUtils:4951 Class[]→Field): cleared via rebindIncompatibleLoadForSink array-type relaxation + var-user registration (JDEC_REBIND_INCOMPATIBLE_LOAD_OFF) - Remaining 4 errLines: JDKUtils:319 x2 (invoke arg slot swap), ObjectReaderBaseModule:793 (invokevirtual receiver slot swap), JSON:4367 (control-flow structuring)
…/arguments to correct reaching store
fastjson2 ObjectReaderBaseModule:793 + JDKUtils:319: value-returning invokevirtual/invokestatic
whose receiver or argument is a local-load bound (at phase-1 time, when opcodeIdToRef was
incomplete) to a DFS-stale wrong-type ref. The FCE (FunctionCallExpression) is pushed on the
phase-1 stack with the receiver/args embedded, so rebindIncompatibleLoadForSink (which only
fires on phase-2 putstatic/putfield/astore sinks) cannot reach them.
Fix: rebindIncompatibleInvokeArgs — a phase-1-post pass (runs after opcodeIdToRef is fully
populated, before phase-2 statement building). For each recorded invoke FCE:
1. Receiver (non-static): rebind if the local-load's type is incompatible with the callee's
declaring class (ObjectReaderBaseModule:793 var7 Annotation[] → Constructor).
2. Arguments: rebind if a local-load arg's type is incompatible with the declared parameter
type (JDKUtils:319 trustedLookup(Class) arg var22_1 MethodHandle → Class).
Both use the structural reachingStoresOf walk to find the true reaching store whose ref type
matches the sink type, then reset the FCE operand's SlotValue to that ref (with var-user
registration so var-fold doesn't fold it away).
Infrastructure: invokeFuncCall map records each invoke opcode → its FCE (populated in all four
phase-1 invoke handlers). findLocalLoadInSource uses slot-based matching (loads are NOT in
opcodeIdToRef, so it finds the store's slot first, then BFS the Source chain for the first
local-load of that slot).
Load-bearing test: invoke-arg slot-swap errors ON=0 OFF=5. tree errLines: 4 → 1
(JDKUtils:319 ×2 + ObjectReaderBaseModule:793 cleared), 8-jar A/B delta ≥ 0.
…8 cleared - fastjson2 table row: 3→1 blocker class, 4→1 tree errLine - #1-5 (JDKUtils): ALL cleared — #1 via catch-param collider (JDEC_EMBED_ASSIGN_DECL_OFF), #2-3 via rebindIncompatibleInvokeArgs (JDEC_REBIND_INCOMPATIBLE_LOAD_OFF) - #18 (ObjectReaderBaseModule:793): cleared via rebindIncompatibleInvokeArgs - Remaining 1 errLine: JSON:4367 definite-assignment (control-flow structuring)
…ti-assign gate to fix definite-assignment fastjson2 JSON.copyTo:4367 — slot 16 holds String (DateUtils.format) on the if-branch and Object (var15/copy()) on the else-branch. The decompiler split it into var16(String) + var17(Object dead-store), and the merge-read at var14.accept(var11_1,var16) reads var16 uninitialized on the else-branch (javac 'variable var16 might not have been initialized'). Fix: initProximateSplitSlotDecl — a dump-layer text pass that appends a default initializer (= null for reference types, = 0 for int/long/short/byte, etc.) to bare 'Type varN;' declarations when EITHER (a) an Object dead-store sibling exists within 10 lines (the split-slot proximity signature), OR (b) varN is assigned ≥2 times (a multi-branch if/else chain where one branch may not cover all paths). Excludes Java keywords (throw/return/etc.) that match the type regex. Kill-switch: JDEC_INIT_PROX_SPLIT_OFF=1. NOTE: this clears JSON:4367 but reveals TypeReference:149 (a pre-existing latent definite-assignment on int var10, same family). Net fastjson2 tree errLines stays at 1 (1→1 trade: JSON:4367→TypeReference:149). The init gate didn't catch TypeReference:149 because its method's sourceCode structure differs. All 8 other jars unchanged (delta ≥ 0). Load-bearing test: JSON.java 'var16 might not have been initialized' ON=0 OFF=1.
… with ≥1 assign Lowers the multi-assign fallback gate for primitive types from ≥2 to ≥1 (int/long/short/ byte/double/float/char/boolean are safe to default-init: = 0, = 0.0, = false, = '\0'). This catches primitive definite-assignment cases where the method's sourceCode only captures one of multiple branch assignments (TypeReference:149 int var10 pattern). Reference types keep the stricter ≥2 gate to avoid over-firing. No regression across 8 jars.
…for complete method-body visibility Previously initProximateSplitSlotDecl ran per-method on chunked sourceCode, which couldn't see assignments in other method blocks (TypeReference:149 int var10 had assignCount=1 instead of 8). Now runs on the full class text after assembly, so countAssignTargets and hasReadInBody see the complete method body. JSON:4367 still cleared (load-bearing ON=0 OFF=1). All 8 jars unchanged (delta ≥ 0). TypeReference:149 remains (the getType method's int var10; declaration is at a different class-text position that the init gate still doesn't reach).
…ndered outside (OWC try/catch structuring fix) ObjectWriterCreator.createFieldWriter: getDeclaredConstructor() and newInstance() (which throw NoSuchMethodException, InstantiationException, etc.) were rendered OUTSIDE the try block, while the catch clause listed those exception types. javac rejected: 'exception never thrown' and 'unreported checked exception'. Fix: fixTryCatchExceptionPlacement — a dump-layer text pass that detects the pattern (if-block with exception-throwing calls immediately followed by a try/catch that lists those exception types) and moves the calls into the inner try body. Kill-switch: JDEC_FIX_TRYCATCH_OFF=1. This fix is defense-in-depth: it clears OWC exception errors that are currently MASKED by TypeReference:149 (javac tree-compile error masking). When TypeReference:149 is eventually fixed, this fix prevents the OWC exception errors from surfacing. No regression across 8 jars (all unchanged when the masking error is present).
…(§8a ⑥) Corrects the prior conclusion that the TypeReference:149 cascade was '28+ unsolvable across 8 classes'. Deep investigation this session proved the cascade is resolvable layer-by-layer: - L1 TypeReference var10: bare int + if/else chain missing final-else. Fixable via initProximateSplitSlotDecl (remove early-return + chain- LacksFinalElse gate + lambda-capture-safe skip). - L2 ObjectWriterCreator (8 latent): var9_3 use-before-def, lv4_11_3 conditional-assign, var5/var9/var11 lambda-captured. Fixable via method-scoped depth-aware lambda-capture skip (nameCapturedByLambda- Method) + hasRead control-keyword fix. - L3 JSONWriter (4 latent): JSONWriterUTF16/JSONWriter slot-split, return reads var1 (assigned only in JDK8 branch). Fixable via hasRead treating 'return varN' as a read (control keyword not a type token). The ONLY hard barrier is L4 ObjectReaderCreator: var14_3/var13_2 are reassigned each loop iteration and captured by an outer lambda → not effectively-final. This is a structural capture error (not DA) requiring the final-copy technique (insert final local before lambda + rewrite references). That is a separate high-risk structural rewrite. Reverted all code changes because the chainLacksFinalElse text-level DA analysis is fragile (false-positives on BoolVarCopy's else-body with post-assign inner if) and conflicts with load-bearing/golden tests that assert bare declarations are preserved. The analysis path is now documented for focused follow-up work.
…ascade via method-scoped init
Resolves 5 layers of the fastjson2 tree-compile error-masking cascade that
were previously masked behind TypeReference.java:149 (var10):
- L1 TypeReference.canonicalize int var10: bare decl + if/else chain missing
final-else. Fixed by removing initProximateSplitSlotDecl's early-return
(which skipped primitive gating when no Object dead-store existed) and
adding chainLacksFinalElse detection.
- L2 ObjectWriterCreator (var9_3/lv4_11_3 DA + lambda captures): fixed by
method-scoped countAssignTargets/hasReadInBody (avoids cross-method varN
name-reuse inflation) + nameCapturedByLambdaMethod (depth-aware capture
detection: skip init for vars read in a DEEPER lambda + assigned) +
hasRead treating control keywords (return/throw/if) as reads, not decls.
- L3 JSONWriter (JSONWriterUTF16/JSONWriter slot-split return): fixed by
hasRead control-keyword fix (return varN was misread as a declaration).
- L4 JSONFactory static-init var11: fixed by isMethodOrInitBlockStart
supporting static {} initializer blocks and constructor signatures.
- ORC var16/var25/var17 (Object bare decls): fixed by Object-exclusion only
when JDEC_REF_SLOT_NULL_REASSIGN_MERGE_OFF is set.
The remaining 2 fastjson2 errors are ObjectReaderCreator:339/348 — var14_3
(Class)/var13_2 (String) are reassigned each do/while iteration AND captured
by an enclosing lambda, a STRUCTURAL effectively-final error (not DA) that
requires a final-copy rewrite. fixLambdaLoopCapture implements this but is
DISABLED (JDEC_LAMBDA_LOOP_CAPTURE_COPY_ON=1 to enable): the body-span and
rewrite logic is unreliable for multi-capture / large-body lambdas, causing
widespread 'cannot find symbol'. That rewrite needs structured statement-
scope analysis before it can be enabled safely.
Net 8-jar A/B: commons-lang3 11→10 (-1), snakeyaml 1→0 (-1), fastjson2 1→2
(ORC capture unmasked), all others unchanged. Updated 7 load-bearing/
golden tests to tolerate default-initialized declarations (they still
verify the merge/split decisions, just not bare-vs-initialized).
…le-break fixes Resolves the remaining fastjson2 lambda-capture and switch-control-flow errors that were unmasked after the TypeReference→OWC→JSONWriter cascade: fixLambdaLoopCapture (now ENABLED, JDEC_LAMBDA_LOOP_CAPTURE_COPY_OFF=1): - Per-capture-point final-copy: each lambda body in a distinct branch/scope gets its own copy (one copy cannot cover all). Inserted before the lambda- open line (or at the enclosing statement start for deeply-nested lambdas via findEnclosingStmtStart). - lambdaLineIsStmtStart guards: skip return-statements, multi-line argument continuations (lines starting with `)})`, `,(`), and deep-nested args (parenDepth ≥ 2 unless handled via statement-start insertion). - Runs BEFORE initProximateSplitSlotDecl so the original variable (no longer read in the lambda after rewrite) can be safely default-initialized. - Resolves ObjectReaderCreator var14_3/var13_2, ObjectWriterCreator/ ObjectReaderCreator captures, JSONPathTypedMultiNames var12. removeUnreachableSwitchBreak (JDEC_RM_UNREACHABLE_BREAK_OFF=1): drops a `break;` immediately following a nested switch whose default throws/returns (javac: 'unreachable statement'). Resolves JSONReaderJSONB:5334. fixEmptySwitchDefault (JDEC_FIX_EMPTY_SWITCH_DEFAULT_OFF=1): inserts `throw new RuntimeException();` into an empty switch `default:` label, completing control flow in value-returning methods. Resolves JSONReaderJSONB:4584 (readLocalDate0) and :5337. fastjson2 tree: 2→1 errLine. The remaining error is ObjectReaderBaseModule getObjectReader:1922 'missing return statement' — a 30+-deep nested if/else chain where one branch lacks a return; requires control-flow analysis to localize reliably (fixMissingReturn attempt was too broad: 275 regressions, disabled). Net 8-jar A/B: commons-lang3 11→10 (-1), snakeyaml 1→0 (-1), spring 28→30 (+2, unmasked ClassReader cascade — same masking phenomenon as fastjson2), all others unchanged. All classparser tests pass.
…tch + addMissingCatchException (disabled)
fixMissingReturn (ENABLED, JDEC_FIX_MISSING_RETURN_OFF=1): inserts `return null;`
after a no-op empty-if (`if(cond){};`) that is the last statement of a block containing
a switch with a `default:` clause. This is the control-flow pattern producing 'missing
return statement' in deep switch/if chains (javac does not treat a non-constant
String-switch as definitely-terminating even with default:return). Gated to reference-
return methods only (enclosingReturnsReference) to avoid 'null cannot be converted to
boolean' on primitive returns. Resolves ObjectReaderBaseModule.getObjectReader:1922.
addMissingCatchException (DISABLED, JDEC_ADD_MISSING_CATCH_ON=1): augments a catch
clause's type list when the try body calls a reflection method (getConstructor etc.)
that throws NoSuchMethodException not among the caught types. Implemented with proper
char-by-char brace-depth tracking (to handle }catch(...){ mid-line) and word-boundary
exception matching (to avoid subclass conflicts). DISABLED because it cascades: each
reflection call site needs its own catch, and augmenting one place unmasks the next
(snakeyaml, spring regressions). Needs per-call-site scope analysis to enable safely.
fastjson2 tree: 1→1 errLine (getObjectReader resolved; ORCASM unreported
NoSuchMethodException is the new remaining error — needs addMissingCatchException
enabled without cascade, or per-call-site try/catch restructuring).
8-jar A/B: commons-lang3 -1, snakeyaml -1 (improvements), spring +2 (unmasked
ClassReader cascade), all others unchanged. All classparser tests pass.
…ed exception-flow analysis
Rewrites addMissingCatchException to use per-call-site scope (the verifier's
specification) instead of global text scanning:
- Pre-computes all try/catch blocks (tryStart, tryBodyEnd, catchLine, caughtTypes).
- For EACH reflection call site (getConstructor/getDeclaredConstructor/getMethod/
getDeclaredMethod), walks the enclosing try/catch chain.
- Lambda-scope boundary: hasLambdaBoundaryBetween excludes try blocks that are
separated from the call by a `-> {` lambda body (a lambda is a separate scope;
an outer try/catch cannot catch exceptions thrown inside it). This was the key
fix for ORCASM:3577 — the outer `catch(Throwable)` was outside the lambda.
- Augments the innermost enclosing catch with NoSuchMethodException ONLY if no
enclosing catch already handles it (or a supertype: ReflectiveOperationException,
Exception, Throwable), using word-boundary matching.
- catchRethrowsVariable guard: skips catches that rethrow the caught variable
(`catch(E var){ throw var; }`) — augmenting would propagate a checked exception
requiring a throws declaration. Fixes snakeyaml ConstructScalar regression.
- catchBodyAccessesCatchVar guard: skips catches that access members of the catch
variable (e.g. `var.getTargetException()`) — widening the multi-catch narrows
the inferred type and breaks the member access. Fixes spring ReflectUtils
regression (`cannot find symbol getTargetException`).
Resolves ObjectReaderCreatorASM:3577 (unreported NoSuchMethodException) with
ZERO regression: spring 30 (unchanged), snakeyaml 0 (improved), all others
unchanged. fastjson2 now 2 (ObjectReaderImplMap nested-try dedup, next layer).
…rom outer catch) dedupNestedCatchException (JDEC_DEDUP_NESTED_CATCH_OFF=1): removes an exception type from an outer catch clause when a nested try/catch within the try body already catches that exact type. Repairs javac 'exception X is never thrown in body of corresponding try statement'. Resolves ObjectReaderImplMap:398 (InstantiationException redundant in outer catch — inner catch already handles it). wrapUncaughtThrowingCall (OPT-IN, JDEC_WRAP_ALLOCATE_ON=1): wraps UNSAFE.allocateInstance() in try/catch(InstantiationException). Fixes ObjectReaderImplMap:386 but unmasks StringSchema fall-through-switch (9 errors, needs addBreakToSwitchCases). Disabled by default pending that fix. fastjson2 tree: 2→1 errLine (ObjectReaderImplMap never-thrown resolved; allocateInstance uncaught remains). 8-jar unchanged (zero regression).
…lMap allocateInstance wrapUncaughtThrowingCall (JDEC_WRAP_ALLOCATE_ON=1, opt-in): wraps UNSAFE.allocateInstance() in try/catch(InstantiationException). Fixes ObjectReaderImplMap:386 but unmasks the StringSchema fall-through-switch layer (9 'variable might already have been assigned' errors). addBreakToSwitchCases (JDEC_ADD_SWITCH_BREAK_ON=1, opt-in): inserts break; at end of switch case bodies lacking a terminator. Fixes StringSchema when combined with wrap, but causes widespread 'unreachable statement' regressions across other jars (codec +2, gson +13, snakeyaml +7, spring +3) — the if/else-terminator vs lambda-body-return distinction needs full control-flow analysis (CFA) to be reliable. DISABLED pending structured-flow work. fastjson2 tree: 1 errLine (ObjectReaderImplMap allocateInstance uncaught). The path to 0 requires: wrap-fix (ObjectReaderImplMap) + a CFA-correct switch-break fix (StringSchema) that distinguishes case-terminating control-flow constructs from lambda-body returns. Both functions are implemented but opt-in/disabled. 8-jar: ALL unchanged (zero regression). All classparser tests pass.
…-body indent blocks reliable terminator detection
Refined addBreakToSwitchCases with single-statement/lambda-assignment eligibility
gating and bodyIndent-restricted terminator scanning. Fixes the simple assignment
cases in StringSchema but the lambda-body cases (case 3/7) still fail: the dumper
renders lambda bodies at the case-body indent, so `return` statements inside the
lambda are mis-detected as case terminators. This requires lambda-aware control-flow
analysis (tracking `-> {` scope boundaries within case bodies) to distinguish
case-terminating control-flow from lambda-body returns.
Both wrapUncaughtThrowingCall and addBreakToSwitchCases are implemented but OPT-IN/
DISABLED. The verified clean state (dedupNestedCatchException enabled) keeps
fastjson2 at 1 errLine (ObjectReaderImplMap allocateInstance) with ZERO regression
across all 8 jars.
…ext terminator check (disabled)
Improved addBreakToSwitchCases with lambda-scope tracking: during case-body
terminator scanning, tracks `-> {` lambda depth so return/break/continue
inside lambda bodies don't count as case terminators. Added pre-lambda-text
check: for lines like `return X(..., (l0) -> {` that have BOTH a case-level
terminator AND a lambda-body open, the terminator is detected from the text
before `->` (the case-level portion).
This correctly handles StringSchema's lambda-case (case 3/7) and ObjectReader-
BaseModule's return-with-lambda case (case 14). However, the pre-lambda-text
check interacts with addMissingCatchException to cause LambdaMiscCodec
regressions (42 errors) when both are enabled — the addBreak changes expose
catch-augment sites that then cascade. Both wrap and addBreak remain DISABLED
pending a unified structured-flow pass that coordinates all dump-layer fixes.
fastjson2=1 (ObjectReaderImplMap allocateInstance). 8-jar: zero regression.
…nt) + document LambdaMiscCodec cascade Reordered the dump-layer pass sequence so addBreakToSwitchCases and wrapUncaughtThrowingCall run AFTER addMissingCatchException (the catch-augment sees the pre-break structure). Verified that the verifier's suggested alternative (addBreak before addMissingCatchException) does NOT resolve the cascade: the 42-error LambdaMiscCodec regression is NOT an addBreak/addMissingCatchException interaction — it's a NEW masked layer (switch-case getMethod calls throwing uncaught NoSuchMethodException, needing per-case try/catch wrapping). Fixing StringSchema (via addBreak) unmasks LambdaMiscCodec, which unmasks further layers. This confirms the cascade is effectively unbounded: each structural fix reveals the next masked layer (different error type each time). wrap and addBreak remain OPT-IN (disabled by default). fastjson2=1 (ObjectReaderImplMap allocateInstance). 8-jar: zero regression.
… default-skip dedupNestedCatchException: when ALL caught types in an outer catch are handled by inner catches (kept would be empty), replace with 'Exception' (catch-all supertype) to keep the catch clause valid without claiming specific never-thrown types. Resolves LambdaMiscCodec case-23 (8-layer nested try/catch, 21 errors). wrapReflectionCallInSwitchCase (JDEC_WRAP_REFLECTION_CASE_OFF=1): wraps switch- case reflection calls (getMethod etc.) in try/catch. Added switch-case detection (scan backward through try/catch nesting for case label). addBreakToSwitchCases: default-skip guard (don't add break before default: label). Lambda-aware CFA retained. Enabled: wrap + wrapReflection + addBreak + dedup Exception-fallback. fastjson2: 9→1 (StringSchema:194 — one lambda-case remains). LambdaMiscCodec resolved (was 42). ObjectReaderImplMap resolved. codec: 0→1 (MurmurHash3 addBreak regression — the default-skip doesn't catch all fall-through patterns). Needs further addBreak refinement.
…hCases Implemented dependency analysis distinguishing: - CONFLICT: case and default both independently assign the same variable (e.g. StringSchema: case assigns formatValidator, default assigns null) → add break to prevent 'might already have been assigned' - DEPENDENCY: default's assignment READS the case's variable (compound assignment like codec MurmurHash3: var5 = var5 ^ x) → skip break (default depends on case's fall-through value) Added collectSwitchAssignedVars (with field-assignment support), collectSwitchDefaultSelfReads, defaultAssignsIndependently. fastjson2: 1 errLine (StringSchema:194 — case 8 break not yet applied correctly, dependency analysis detects conflict but insert point needs fixing). codec: 1 errLine (MurmurHash3 — regression under investigation). All classparser tests pass.
…analysis The verifier's specification fully implemented: addBreakToSwitchCases now distinguishes CONFLICT (case and next-case/default both independently assign the same variable → add break) from DEPENDENCY (next case's assignment reads the variable in a compound assignment like var5 = var5 ^ x → skip break, fall-through is intentional). Key fixes: - defaultAssignsIndependently: removed the field-access skip (this.formatValidator IS a relevant assignment). This was the root cause of StringSchema case 8 'NO conflict' mis-detection. - Generalized conflict detection: now checks ANY next case (not just default). If this case and the next case both independently assign the same variable, add break. If the next case reads the variable in a compound assignment, skip break (computation chain like codec MurmurHash3). Results: - StringSchema: FULLY RESOLVED (all 9 formatValidator 'might already have been assigned' errors fixed via conflict-based breaks). - codec MurmurHash3: ZERO regression (compound-assignment dependency correctly detected → fall-through preserved → no missing-return error). - codec: 0→0 (zero regression). spring/snakeyaml/gson/guava all unchanged. - fastjson2: 1→4 (MoneySupport unmasked — next layer, same unreported-checked- exception pattern as before). All classparser tests pass.
…alls (MoneySupport) Extended wrapReflectionCallInSwitchCase to also handle reflection/method-handle calls inside lambda bodies that are NOT covered by an enclosing try/catch (due to the lambda scope boundary). Added: - isInsideLambdaBody: detects calls inside lambda scope. - lambdaChainRe: matches invokeExact/metafactory/findStatic/findVirtual calls (which throw LambdaConversionException, NoSuchMethodException, Throwable). - catchType field: NoSuchMethodException for reflection calls, Throwable for LambdaMetafactory chains. Resolves MoneySupport L140 (getMethod in lambda) and L61 (LambdaMetafactory chain in lambda). fastjson2: 4→1 (BeanUtils var2 lambda-capture — next layer, needs fixLambdaLoopCapture to handle it). codec=0, spring/snakeyaml/gson all clean.
…tProximateSplitSlotDecl Extended bareDeclRe to also match declarations with `= null` initializer (`Type varN = null;`). When such a variable is captured by a lambda AND has exactly 1 reassignment (countAssignTargetsMethod == 1), strip the `= null` initializer (→ `Type varN;`) to make it effectively-final (only the later reassignment remains). This repairs 'local variables referenced from a lambda expression must be final or effectively final'. Resolves BeanUtils:3619 (var2 declared `ArrayList var2 = null;`, reassigned `var2 = new ArrayList();`, captured by declaredFields lambda → stripped to bare `ArrayList var2;` → effectively-final → lambda capture valid). fastjson2: 1→1 (JdbcSupport$ClobWriter:15 unreported NoSuchMethodException — next masked layer). All 8 jars: zero regression (codec=0, spring=30, snakeyaml=0).
…tion calls
wrapFieldInitializerReflection converts a field initializer containing a
reflection call (getMethod etc.) into a bare field declaration + instance/
static initialization block with try/catch. This repairs 'unreported exception
NoSuchMethodException' in field initializers (which cannot contain try/catch
in Java).
Pattern: `final Function function = ...CLASS_CLOB.getMethod(...);`
→ `final Function function;` + instance-init block:
`{ try{ function = ...getMethod(...); } catch(NoSuchMethodException e){
throw new RuntimeException(e); } }`
Resolves JdbcSupport$ClobWriter:15 — the last fastjson2 tree-compile error.
🏆 FASTJSON2 = 0 ERROR LINES 🏆
The entire TypeReference→...→JdbcSupport cascade (15 layers) is now resolved
through 16 commits implementing: method-scoped DA init, lambda-aware CFA,
per-call-site exception-flow analysis, final-copy lambda-capture repair,
switch-default/unreachable-break fixes, nested-try dedup, switch-break with
conflict/dependency analysis, reflection-call wrapping (switch-case, lambda-body,
and field-initializer), and null-init stripping for captured variables.
8-jar A/B vs original baseline: codec 0→0, commons-lang3 11→10 (-1),
fastjson2 1→0 (-1), snakeyaml 1→0 (-1), gson/guava/jsoup unchanged,
spring 28→30 (+2 unmasked ClassReader cascade). All classparser tests pass.
…refresh status ledger fastjson2 2.0.43 and snakeyaml 2.2 now both reach the full north-star chain: decompile -> 0 tree errors -> repackage -> java -Xverify:all all classes pass (689/689 and 233/233 respectively). The TypeReference DA cascade (missing final-else on int var10) was cleared by the method-scoped init (ff8e753) and the L4 ObjectReaderCreator effectively-final capture errors by the final-copy lambda-capture technique (1e812bd). snakeyaml's createNumber T4b definite-assignment was likewise cleared by the initProximateSplitSlotDecl family. - jar_roundtrip_test.go: generalize the single 'codec' hard-assert into a provenClean set (codec/gson/fastjson2/snakeyaml) so any regression in tree errors or verify failures on these 4 jars fails CI loudly. - CODEC_TODO.md: refresh §1 table to real numbers (fastjson2 1->0, snakeyaml 1->0, spring 25->30 errLines, commons-lang3 11->10, totals 50/68 @ 98.9%), mark §8a fastjson2 fully cleared, update §2 to note fastjson2 no longer the slot-reuse tail.
…ss-arm variable copy jsoup XmlTreeBuilder.insert (Token.Comment): the slot holding the parsed node is first declared as the narrow arm type (Comment) but a later sibling arm stores XmlDeclaration, so the arm-merge widens the slot ref to the cross-class LUB (Node). The first declaration `Comment var3 = var2` still rendered with the RHS value's type (Comment) instead of the widened slot type (Node), making the XmlDeclaration reassign fail javac with "XmlDeclaration cannot be converted to Comment". Add refSlotWiderThanLUB: when the slot ref (LeftValue.Type) was widened by a sibling-arm merge to a non-Object LUB that strictly contains the initializer's type, and the RHS is a variable read (a cross-arm copy of another JavaRef), adopt the slot ref type for the declaration. Object is excluded (member-access receiver regressions). The RHS-is-JavaRef gate avoids widening away a method-call/new-expression initializer's declared return type, which would smash downstream member access (spring ObjectToObjectConverter.getValidatedExecutable regressed -1 when widened to the wrong LUB Member without the gate). jsoup tree 1->0 (241/241 verify); full 8-jar A/B delta >= 0 (spring unchanged, all other jars unchanged). Kill-switch JDEC_REF_SLOT_LUB_DECL_OFF. Lock jsoup into jar_roundtrip_test.go provenClean hard-assert.
…les (O[]) JDEC_LAMBDA_RETURN_TYPEVAR_CAST_OFF recovered the source's unchecked `return (T) expr;` cast on a generic method returning Supplier<T> / Function<T,R> whose lambda body returned the erased Object. Two gaps left it unable to fire for an ARRAY type variable: 1. lambdaReturnPositionTypevar only matched an instantiatedMethodType return of `java.lang.Object`. For an array-typed-variable SAM (Function<List<O>,O[]>.apply) the erased return is `Object[][]`, not Object, so the gate returned "". Accept Object[] too. 2. resolveLambdaReturnTypevar only resolved a scalar bare type variable (T) from the enclosing method Signature's return-position type arg. For `O[]` the type arg parses as a JavaArrayType whose element is the bare variable O; recover `O[]` from the element. injectLambdaReturnCast already emits `(O[]) (expr);` for the resulting string. Real hit: commons-lang3 Streams$ArrayCollector.finisher returns Function<List<O>,O[]>; the lambda body returns list.toArray((Object[]) Array.newInstance(elementType, size)), an erased Object[] the source cast back to O[]. Now renders `return (O[]) (l0.toArray(...))`. commons-lang3 tree 10->8 errLines (2 Streams$ArrayCollector bad-return-type-in-lambda sites cleared); full 8-jar A/B delta >= 0 (fastjson2/gson/snakeyaml/jsoup 0, guava 27, spring 30 unchanged). Existing scalar LambdaReturnTypevarSeed golden still passes.
…d ambiguity commons-lang3 FieldUtils.readStaticField/readDeclaredStaticField/writeStaticField/writeDeclaredStaticField call readField/writeField with a null argument, but readField has two same-arity overloads (readField(Field,Object,boolean) vs readField(Object,String,boolean)) and writeField likewise — javac flags `m(Field,null,boolean)` as ambiguous (null assignable to both Object and String, neither overload more specific). The bytecode descriptor pins the chosen formal; casting null to that formal (`(Object) null` is no longer assignable to String) forces javac to pick the bytecode-selected overload. The earlier ungated attempt smashed generic-call type-variable inference (fastjson2 JSONReader.readBytes regressed 0->33 because `(Object) null` changed overload selection and the return type became Object instead of byte[]). Gate the cast on GENUINE overload ambiguity: - dumper.go populates a new MethodDescriptors map (every (name,descriptor) on same-class methods, regardless of Signature) so the renderer can detect same-arity overloads. - class_context.HasOverloadedSameArity reports whether a second same-arity overload with a different descriptor exists; only then does nullArgDisambiguationCast emit `(FormalType) null`. - A non-overloaded method's null arg stays bare — javac inference is never touched. commons-lang3 tree 8->5 errLines (3 FieldUtils ambiguous-reference sites cleared); full 8-jar A/B delta >= 0 (fastjson2/gson/snakeyaml/jsoup 0, guava 27, spring 30 unchanged). classparser regression seeds + TestAtomicRefVParamArgCastIsLoadBearing (fix-OFF bare-arg assertion) pass. Kill-switch: JDEC_NULL_ARG_CAST_OFF.
… class commons-lang3 StrSubstitutor.substitute: `var24_1.length()` failed 'cannot find symbol: method length()' because var24_1 was declared `Object var24_1 = null;` — its null-init never adopted a concrete type because slot 24 is REUSED across three disjoint variables (var24 char[], var24 String, var24_1 Object), so the null-init ref's nullInitDefDominates gate blocked adoption and the concrete String store minted a separate ref. The bytecode at the member access is `aload 24; invokevirtual String.length`, so the REAL receiver type is String. Add objectReceiverInvokeCast: when an invoke receiver is a plain java.lang.Object-typed local (a JavaRef, not this/param/expression) and the bytecode invoke target class (FunctionCallExpression.ClassName) is a concrete reference type distinct from Object/array, wrap the receiver in `((TargetClass)(recv)).m(...)` so the member resolves against the real type. Gated tightly (Object-typed ref, non-Object non-array target, non-<init>); mirrors what javac would synthesize for a source-level checkcast or the declared type the slot should have had. The earlier slot-base grouping attempt (keying narrowNullInitObjectDecl by slot base) failed because slot 24's base name groups var24 char[] AND var24 String (genuinely different variables), so the rhsTokens count disagreed; this render-time cast sidesteps the slot-association problem entirely. commons-lang3 tree 5->4 errLines (StrSubstitutor:352 cleared); full 8-jar A/B delta >= 0 (fastjson2/gson/snakeyaml/jsoup 0, guava 27, spring 30 unchanged). classparser regression seeds pass. Kill-switch: JDEC_OBJECT_RECV_INVOKE_CAST_OFF.
…pe ctor sig recognition Two fixes, both zero-regression (8-JAR A/B delta >= 0): 1. sameClassStaticMethodTypeVarArgCast (JDEC_SAMECLASS_STATIC_TYPEVAR_ARG_OFF): An instance method reads a field typed by the bare class type variable T at its erased Object static type, then passes it to a same-class STATIC generic method whose formal is also T (commons-lang3 Range between(T,T,Comparator) from intersectionWith). The source (T) cast erased to a no-op, so the bare render makes javac feed Object to the generic method, breaking inference (incompatible bounds). Re-emit (T) unchecked. Gated: static same-class method, formal is class-scope typevar, arg erased to Object, call site is instance method (funcCtx.IsStatic). Fixes commons-lang3 Range.intersectionWith (4->3). Load-bearing test: sameclass_static_typevar_arg_test.go. 2. Intersection-type constructor signature recognition: buildSiblingCtorSig rejected Signatures starting with < (method-scope type params prefix), so a ctor like guava Invokables <M extends AccessibleObject & Member> Invokable(M) was invisible to calleeParamIsErasedTypeVar. The arg-cast logic then upcast to the erased FIRST bound (AccessibleObject), losing the Member bound. Fix: accept < prefix, use ParseMethodSignatureFull to count params; refactor calleeParamIsErasedTypeVar to try SiblingCtorSig first for init calls. Fixes guava Invokable$MethodInvokable (27->26). CFR/Vineflower both emit bare super(method), confirming de-cast is correct. Status: codec/gson/fastjson2/snakeyaml/jsoup 0 (provenClean), commons-lang3 3, guava 26, spring 30. Total 59 (was 62).
…e 8 false-positive java.util.concurrent.Flow (Java 9+) is absent from --release 8 ct.sym. Any jar whose dependency API references Flow.Publisher (e.g. mutiny's UniConvert.toPublisher / UniCreate.publisher(Flow.Publisher)) fails to compile with "cannot access Flow; class file for java.util.concurrent.Flow not found" -- same shape as sun.misc.Unsafe and jdk.jfr. Extract Flow + Flow$Publisher/Subscriber/Processor/Subscription class files from the running JDK jrt:/ image and place them on the classpath, making the symbol visible to javac without changing any decompiled source. Eliminates spring MutinyRegistrar "cannot access Flow" x2 (spring tree 30->29). Zero regression across all 8 jars.
…ay return type
Two fixes in fixMissingReturn, both zero-regression (8-JAR A/B delta >= 0):
1. Pattern 2: empty-then if-else where the else branch ends with `return`
but the then branch has no return, leaving the method without a return on
the then path ("missing return statement"; spring ASM ClassReader.readStream
`if (var6 == 1) { } else { ... return var7_1; }`). Insert `return null;`
inside the empty then branch. Gated: if-else must be the last statement of
its enclosing block (next non-blank after else's `}` is `}` or `}catch`/
`}finally`), enclosing method returns a reference type. The last-statement
gate prevents unreachable-statement regressions (fastjson2 ValueFilter.of
has `return l2;` after the if-else — correctly NOT matched).
2. enclosingReturnsReference: an array return type (`byte[]`, `Object[]`) is
always a reference type — `return null;` is legal. The old code stripped
`[]` before the primitive check, incorrectly treating `byte[]` as `byte`
(primitive) and returning false. Now checks for `[` before stripping.
Fixes spring ClassReader.readStream "missing return statement" (spring
29->28). Zero regression: fastjson2 stays 0, all 8 jars delta >= 0.
Status: codec/gson/fastjson2/snakeyaml/jsoup 0 (provenClean), commons-lang3 3,
guava 26, spring 28. Total 57 (was 59).
The benchmark self-recompile test (TestBenchmarkSelfRecompile) was missing the withFlow shim that the inventory and round-trip tests already use, so spring's Flow-dependent classes (MutinyRegistrar) counted as false-positive errors in the benchmark report (spring 28 vs 29).
CI lint (gofmt) was failing on multiple files that were never formatted. Run gofmt -w on classparser/, test/cross/, cmd/ to pass the lint check. No logic changes.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
No description provided.