fix: generate missing throws declarations, validate exceptions (#2441)#2475
Conversation
|
@nitram84 thanks! Looks good, although I will need some time to review this PR, couple days I hope 🙂
Hm, it is still unclear for me why rethrow Throwable from catch don't need
Well there is common IMethodDetails which is implemented by MethodNode and ClspMethod.
Not sure what you mean, ArgType.ObjectType already here: and can be created using ArgType.object() (it will also convert to . form, removing L and ;)
|
|
Thank you! This is not an easy PR. Take any time you need for a review. I think found the answer for the special case: https://stackoverflow.com/questions/24981736/why-is-throwing-a-checked-exception-type-allowed-in-this-case void test1() { // no throws required
try {
} catch (Throwable t) {
throw t;
}
}
void test2() throws Throwable {
try {
} catch (Throwable t) {
Throwable t2 = t;
throw t2;
}
} This seems to be a Java 7 feature: if a caught exception is directly rethrown, it is not necessary to add a throws declaration for a more general exception type. At the moment I don't throw exceptions of a try block, if they are handled in catch clause. To fix this I have to remember all thrown exceptions of a try block. For each catch clause I have to check whether an exception is directly rethrown. If yes I have to declare all remembered exceptions of the corresponding try block as thrown. Otherwise I have to declare all thrown exceptions of the catch clause. Nested try catch blocks might be interesting. There might be more fun with early Java 6 apps that don't support that language feature if someone wants to recompile them with Java 6. With my "logical |
|
I reviewed code and have two news:
|
|
Ok, I think it is fine to merge this PR. Processing issue is still there, but to resolving it, will require to apply current instruction checks using low-level code scanner, and that might be hard. |
This PR calculates missing throws declarations and introduces basic validation to fix #2441.
The algorithm is a bit more complex than a simple call graph traversal: for each method thrown exceptions are calculated and throws-annotations are used as a hint. If throws-annotations are compatible with calculated exceptions, the algorithm takes the exception of the throws-annotations. So in normal cases there won't be any difference.
There are two drawbacks:
1.) Option '
no-finally' is active. In this case jadx generates code likeThe algorithm detects a thrown
Throwablehere, but in this special case where no throws declaration is required. I don't see a way to distinguish former finally clauses from other similar try-catch structures where throws declarations are required. I could only disable the pass for 'no-finally'. What is better unneeded throws declarations or possible decompilation issues?2.) Unknown class hierarchy
With an unknown class hierarchy (e.g. decompilation of JARs, smali, ...) I can't perform a validation so assume that thrown classes are a Exception but not a
RuntimeException. In this case unneeded throws declarations might be added too.Some findings:
This algorithm operates on full qualified names and signatures, in other words just strings, the common "base" of
ClassNode/ClspClassandMethodNode/ClspMethod. I'm aware that this types are used to express totally different things, but from a logical perspective they share common concepts:ArgType.ObjectTypeI could imagine common interfaces for both class types and method types. Some passes could be simplified if they can operate on common interfaces. For now we use even different names to access parents.
In my unit tests I don't use any modifiers for test classes and methods to prevent sonarqube warnings.