Issue details
On the java/class input path, IfRegionMaker can drop an else and emit the else-branch plus the shared tail unconditionally. This is a miscompilation (wrong control flow), not just noisy output.
IfRegionMaker.process asks findOutBlock(then, else) for the block where the two branches converge; that block becomes the region exit and everything after it is emitted at the outer level. When a secondary break inside one branch creates an intermediate merge block, the "unique block in the intersection of the two branches' dominator frontiers" heuristic returns that intermediate block instead of the true post-dominator (the block that actually post-dominates both branches). The then region then swallows the real tail (duplicating it), the else region is emitted as a plain continuation so the else is lost, and the intermediate block plus tail are emitted after the if. The else-branch therefore runs unconditionally.
Sample
The trigger is a specific CFG (the else reached by fall-through, plus a secondary break in the then-branch). javac does not emit this shape, so the input is hand-written JVM bytecode. To reproduce, drop the raung below into jadx-core/src/test/raung and decompile (jadx's raung test harness reads it directly), or assemble it with raung-asm's executeForSingleClass and run jadx on the class.
.version 50
.class public super DropElse
.super java/lang/Object
.auto frames
.method public static f(ZZI)V
iload 0
ifne :NOPIC
bipush 10
istore 3
iload 2
istore 4
iload 1
ifeq :TAIL
bipush 30
istore 3
goto :AFTER
:NOPIC
bipush 20
istore 3
:AFTER
iconst_0
istore 4
:TAIL
iload 4
ifeq :T2
iload 3
invokestatic DropElse sink (I)V
goto :T3
:T2
iload 3
ineg
invokestatic DropElse sink (I)V
:T3
iload 3
invokestatic DropElse sink (I)V
return
.end method
.method public static sink(I)V
return
.end method
Intended control flow: exactly one of the two assignments runs, then the shared tail once. CFR 0.152 recovers it correctly with labeled breaks:
public static void f(boolean bl, boolean bl2, int n) {
int n2;
int n3;
block3: {
block4: {
block2: {
if (bl) break block2;
n3 = 10;
n2 = n;
if (!bl2) break block3;
n3 = 30;
break block4;
}
n3 = 20;
}
n2 = 0;
}
if (n2 != 0) {
DropElse.sink(n3);
} else {
DropElse.sink(-n3);
}
DropElse.sink(n3);
}
Current output
master emits (note the two Code duplicated warnings; i2 = 20/i3 = 0 is the else/nopic branch, now unconditional, and the tail is duplicated):
public static void f(boolean z, boolean z2, int i) {
int i2;
int i3;
if (!z) {
i2 = 10;
i3 = i;
if (z2) {
i2 = 30;
}
if (i3 != 0) {
sink(i2);
} else {
sink(-i2);
}
sink(i2);
}
i2 = 20;
i3 = 0;
if (i3 != 0) {
sink(i2);
} else {
sink(-i2);
}
sink(i2);
}
The else/nopic branch runs unconditionally after the real branch's tail. So f(false, false, 5), which should call sink twice (sink(10), sink(10)), calls it four times and ends with o == 20 on master.
Jadx version
master ada2a85e
Issue details
On the java/class input path,
IfRegionMakercan drop anelseand emit the else-branch plus the shared tail unconditionally. This is a miscompilation (wrong control flow), not just noisy output.IfRegionMaker.processasksfindOutBlock(then, else)for the block where the two branches converge; that block becomes the region exit and everything after it is emitted at the outer level. When a secondary break inside one branch creates an intermediate merge block, the "unique block in the intersection of the two branches' dominator frontiers" heuristic returns that intermediate block instead of the true post-dominator (the block that actually post-dominates both branches). Thethenregion then swallows the real tail (duplicating it), theelseregion is emitted as a plain continuation so theelseis lost, and the intermediate block plus tail are emitted after theif. The else-branch therefore runs unconditionally.Sample
The trigger is a specific CFG (the
elsereached by fall-through, plus a secondary break in the then-branch). javac does not emit this shape, so the input is hand-written JVM bytecode. To reproduce, drop the raung below intojadx-core/src/test/raungand decompile (jadx's raung test harness reads it directly), or assemble it with raung-asm'sexecuteForSingleClassand run jadx on the class.Intended control flow: exactly one of the two assignments runs, then the shared tail once. CFR 0.152 recovers it correctly with labeled breaks:
Current output
master emits (note the two
Code duplicatedwarnings;i2 = 20/i3 = 0is the else/nopic branch, now unconditional, and the tail is duplicated):The
else/nopic branch runs unconditionally after the real branch's tail. Sof(false, false, 5), which should callsinktwice (sink(10),sink(10)), calls it four times and ends witho == 20on master.Jadx version
master
ada2a85e