-
Notifications
You must be signed in to change notification settings - Fork 5k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[core] Switch-case constructs with multiple exits may cause decompilation errors #826
Comments
@huku- without test case it is hard to tell exactly cause of the issue, so I just try to describe what happen in RegionMaker and this method: And another example of blocks and regions for test TestSwitchSimple:
public void test(int a) {
String s = null;
switch (a % 4) {
case 1:
s = "1";
break;
case 2:
s = "2";
break;
case 3:
s = "3";
break;
case 4:
s = "4";
break;
default:
System.out.println("Not Reach");
break;
}
System.out.println(s);
} jadx.tests.integration.switches.TestSwitchSimple$TestCls.test(int):void
R(3:0|1|3)
| B:0:0x0000
| |> java.lang.String r0 = null;
| Switch: 4, default: R(2:2|1)
| | B:1:0x0003
| | |> switch((r4 % 4)) {
| | | case 1: goto L_0x0013;
| | | case 2: goto L_0x0016;
| | | case 3: goto L_0x0019;
| | | case 4: goto L_0x001c;
| | | default: goto L_0x0006;
| | |};
| | R(2:5|1)
| | | B:5:0x0013
| | | |> r0 = "1";
| | | InsnContainer:1
| | | |> break;
| | R(2:6|1)
| | | B:6:0x0016
| | | |> r0 = "2";
| | | InsnContainer:1
| | | |> break;
| | R(2:7|1)
| | | B:7:0x0019
| | | |> r0 = "3";
| | | InsnContainer:1
| | | |> break;
| | R(2:8|1)
| | | B:8:0x001c
| | | |> r0 = "4";
| | | InsnContainer:1
| | | |> break;
| | R(2:2|1)
| | | B:2:0x0006
| | | |> java.lang.System.out.println("Not Reach");
| | | InsnContainer:1
| | | |> break;
| B:3:0x000d
| |> java.lang.System.out.println(r0); Notice |
@skylot thank you very much for the information. I have examined this further and I think I understand the problem better now. Since minimizing my test-case is not a priority, I just anonymized it a bit. Here's how my switch-case looks like (I have also attached the smail code): The loop in
Obviously this is not correct. As far as I understand, only B:5:0x0045 is a valid region exit and should be returned by Later on, the correct basic-block (B:5:0x0045) is filtered-out here. In this case, it just happens that valid exits of the switch-case region are only the basic-blocks that post-dominate the switch-case head (B:5:0x0045). This could simplify the logic in As a side note, I believe it's perfectly valid for a switch-case to have multiple exits. For example B:40:0x0197 is another exit of the switch-case region, but as you have pointed out it's not followed by any code. However, it could have been the case that the aforementioned block was followed by valid code leading to a different return location. Since |
@huku- thank you for a nice test case!
Also, I notice that such incorrect
You are right
Yes it valid for exits but here we need find case 22:
throw new IllegalStateException(...); |
@huku- I made a fix for a switch with fallthrough cases. As you suggested I use post-dominance info for search |
Great! The method is decompiled fine now. I only get the following informational message, but this is something that I will look into at a later time.
Closing the issue for now as it looks like it has been resolved. @skylot thank you very much for your time. I wish I had more time to help you out a bit, especially with the post-dominance stuff which looks pretty interesting. |
After messing around with several decompilation failures, I noticed that
processSwitch()
in RegionMaker.java may returnnull
for switch-case constructs with multiple exits. In such a case, the dominance frontier of the switch-case contains multiple basic-blocks andouts.cardinality() > 1
. However, the return value ofprocessSwitch()
, stored inout
, is only set in the followingif
clause, which might not be triggered.When this happens
processSwitch()
returnsnull
andmakeRegion()
terminates prematurely, resulting in several errors informing of lost code (a lost basic-block results in all basic-blocks it dominates being lost as well). This is common case in big switch-case constructs and, if I understand correctly, this is the root cause of various decompilation regressions reported by users.Now the hard part :) Solving this problem, requires
processSwitch()
,processIf()
etc in RegionMaker.java being modified to returnCollection<BlockNode>
instead of a singleBlockNode
. Alternatively, I think the problem can also be solved by using a stack of blocks in addition to the region stack currently used. In any way, I think the overall logic implemented in RegionMaker.java needs to be modified a bit.I'm also currently trying to minimize my test-case which is kinda large.
@skylot feel free to share any thoughts. I have created this issue so that we can coordinate towards solving this.
The text was updated successfully, but these errors were encountered: