fix: harden decompilation for complex CFG and type inference edge cases#2905
Open
PumpkinDemo wants to merge 1 commit into
Open
fix: harden decompilation for complex CFG and type inference edge cases#2905PumpkinDemo wants to merge 1 commit into
PumpkinDemo wants to merge 1 commit into
Conversation
Fix several decompilation failures involving malformed or incomplete IR states produced by earlier rewrite passes on complex CFGs. - Switch break insertion now goes through a shared append helper instead of mutating getSubBlocks() directly, so regions with unmodifiable sub-block views can still be patched. SwitchRegion also supports replacing case containers so switch cases can be wrapped safely. - Type inference stops re-entering an SSA variable whose assign arg was already processed in the current update, preventing propagation cycles in large loop/switch methods with fluent assignments. - Switch out calculation treats a dominance-frontier result pointing back to the current loop header as loop back-edge noise and uses the switch immediate post-dominator instead. - Allow Pair keys to contain null endpoints so finally path traversal can cache partially terminated block pairs without failing during hash lookup. - Remove ordinary unreachable CFG fragments discovered after block tree rewrites instead of throwing from BlockProcessor, reusing the existing unreachable-block cleanup path. - Downgrade type update limit overflows in initial type inference and follow-up type fixes to warning comments, preserving partial output instead of recording a decompilation error. - Initialize missing code variables before marking anonymous constructor capture args as final. - Keep check-cast instructions when removing them would require changing an immutable result type. - Tolerate MOVE instructions without result registers during codegen preparation while preserving wrapped instructions with side effects. - Allow exception handler region building to degrade when a top splitter cannot be found, instead of failing the whole method. - Fallback to instruction dump output when region restructuring overflows on complex CFGs, and guard constructor movement for methods without regions. - TypeUpdate.sameFirstArgListener now stops propagation when the counterpart arg is missing, since some CONST instructions can survive as literal/wrapped inputs after their result was removed by earlier passes. - TernaryMod now treats a const instruction as inlineable only when it still has a result, falling back to the PHI argument otherwise. Add regression coverage for all of the above: non-mutable switch break targets, switch case container replacement, loop-header switch out recovery, the type inference propagation cycle, null Pair keys, orphan block cleanup in BlockProcessor, type update limit handling, missing exception splitters, MOVE cleanup without result registers, immutable cast-result checks, and a CONST instruction without a result in sameFirstArgListener.
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.
Note: The changed code and this PR summary are generated by AI. I encountered many errors when using jadx. But it's too hard for a freshman in decompilation field to fixed them manually. So I ask AI to do it. If AI-generated code is not welcomed by this project, just reject this PR. Or if there is any unsuitable code change, let me know please.
Summary
This PR hardens jadx decompilation for several recoverable edge cases in complex control-flow graphs, region reconstruction, type inference, and late code generation.
Previously, these cases could produce hard decompilation errors such as
UnsupportedOperationException,NullPointerException,Unreachable block, type update limit failures, or missing SSA/CodeVar failures. In most of these situations jadx can still produce useful output, so this PR either recovers the structure, safely skips invalid propagation, removes unreachable CFG fragments, or downgrades bounded overflow conditions to warning comments.Fixed Issues
1. Synthetic switch break appended to immutable region sub-blocks
SwitchBreakVisitorpreviously appended syntheticbreakcontainers by mutatingregion.getSubBlocks()directly.Some region implementations expose immutable sub-block lists. For example, a switch case body can be an
IfRegion, andIfRegion#getSubBlocks()returns an unmodifiable list.Example shape:
If jadx needed to append a synthetic
breakaftercase 1, the old code could throwUnsupportedOperationException.The fix adds a controlled append path:
Region.Region.replaceSubBlock.SwitchRegioncase containers.2. Switch inside loop selecting the loop back-edge as switch out block
When a
switchis inside a loop, a case or default branch can flow back to the loop start.Example:
The loop start is a back-edge target, not the switch out block. In some CFG shapes jadx could select the loop start as
SwitchRegion.out, producing an invalid region structure.The fix keeps the current loop context while resolving switch exits and falls back to the immediate post-dominator when the selected out block is actually the loop start.
3. Type propagation re-entering the same SSA assignment
Type propagation can revisit the same SSA assignment through use chains in branch-heavy methods.
Example:
Repeated across many switch cases, this can make type update processing re-enter an assignment already handled by the current update intent.
The fix skips re-processing an SSA assignment if the current
TypeUpdateInfoalready processed it.4. Type propagation assuming instructions always have result registers
Some later passes assumed instructions still had a result register after previous simplification removed it.
Example IR:
The fix adds result-null guards in the affected type propagation paths, so these passes skip or safely reject updates when no result register exists.
5. Ternary simplification assuming inlined const instructions always have results
TernaryModcan see wrapped or inlined const instructions whose result register was already removed.Example IR:
The fix checks for a missing result before trying to update or reuse the const assignment result.
6. MOVE cleanup assuming result register exists
PrepareForCodeGenremoves uselessMOVEinstructions, but it previously assumed everyMOVEstill had a result register.Example IR:
The fix treats a
MOVEwithout result as removable instead of failing during cleanup.7. Check-cast removal conflicting with immutable result type
ModVisitorcould remove aCHECK_CASTand force the cast type onto the result register even when that result was marked withIMMUTABLE_TYPE.Example:
The fix rejects the check-cast result type update when it conflicts with an immutable result type.
8. Missing top splitter for exception handlers
Exception-region building expected every handler path to have a top splitter. Some malformed or transformed CFG shapes can contain a handler block without that splitter.
The fix adds a nullable lookup path and lets region building fall back without aborting the whole method when the top splitter is missing.
9. Region construction overflow reported as a hard error
Very complex, irreducible, or state-machine-like CFGs can exceed region construction limits.
Instead of recording a hard error for
JadxOverflowExceptionorStackOverflowError, the fix clears the method region and letsMethodGenuse fallback instruction output in AUTO mode. A warning comment is still emitted so thereason is visible.
10. Missing CodeVar or SSA metadata in late passes
Some late-stage IR objects can survive without fully initialized
CodeVaror SSA metadata.Example SSA state:
Example catch argument state:
The fix initializes missing
CodeVaror SSA metadata before late reads inFinishTypeInferenceand catch-block code generation.11. Null endpoints in finally traversal cache keys
Finally extraction caches searched block pairs.
Example cache key:
Pair#hashCode()previously assumed both values were non-null, so using such a pair as aHashMapkey caused an NPE.The fix makes
Pairequality and hashing null-safe.12. Unreachable blocks after CFG rewrites
Later block-tree rewrites can produce new orphan CFG fragments.
Example shape:
Previously, non-special unreachable blocks found by the final check caused a hard failure:
The fix reuses the existing unreachable-block removal path instead of throwing, while preserving the existing special handling for split-cross blocks.
13. Type update limit overflow reported as a hard error
Large or highly connected methods can exceed the configured type update limit.
Example error:
This limit is a guard against excessive propagation, not necessarily corrupted IR. The method can still often be emitted with partial type information.
The fix downgrades update-limit overflows in both initial type inference and later type fixes to warning comments. Other type inference exceptions are still reported as errors.
Tests
Added or updated regression coverage for:
PairkeysCommands run:
Additional local validation was done by running
jadx-cliwith--show-bad-code --comments-level debug --log-level erroron large Android bytecode inputs and confirming that the fixed cases no longer produce hard decompilation errors.