-
Notifications
You must be signed in to change notification settings - Fork 5.5k
Description
Issue details
Jadx generates a throws declaration for each method annotated with dalvik.annotation.Throws. But this annotation is optional an not used at runtime ( https://stackoverflow.com/questions/14062434/throws-and-annotation-for-exception-in-dalvik-bytecode ), therefore can be removed ( https://github.com/facebook/redex/blob/main/config/aggressive.config#L62 ). If throws annotation are missing, the decompiled result has compile errors.
If throws annotation are not used at runtime, there is a possibility for bytecode manipulations. I did some tests: a thrown object can be anything and not necessarily a Throwable.
In my opinion jadx has to perform the same checks like a java compiler:
Collect all thrown exceptions for each method, calculate effective throws declarations, verify all throws annotations:
For each method: Follow the call graph of each method invocation; per method; aggregate all directly thrown exceptions and exceptions thrown by method invocations, merge collected exceptions with existing throws annotations, generate missing annotations and mark method as visited.
Is there an example for a visitor where we are traversing the call graph of each method? What is the best way to mark visited methods? A new JadxAttr?
Example:
###### Class jadx.test.MissingThrowsTest (jadx.test.MissingThrowsTest)
.class public Ljadx/test/MissingThrowsTest;
.super Ljava/lang/Object;
.source "MissingThrowsTest.java"
# direct methods
.method public constructor <init>()V
.registers 1
.line 6
invoke-direct {p0}, Ljava/lang/Object;-><init>()V
return-void
.end method
.method private exceptionSource()V
.registers 3
.line 8
new-instance v0, Ljava/io/FileNotFoundException;
const-string v1, ""
invoke-direct {v0, v1}, Ljava/io/FileNotFoundException;-><init>(Ljava/lang/String;)V
throw v0
.end method
.method private invalidException()V
.registers 3
.annotation system Ldalvik/annotation/Throws;
value = {
Ljava/lang/String;
}
.end annotation
.line 36
new-instance v0, Ljava/io/FileNotFoundException;
const-string v1, ""
invoke-direct {v0, v1}, Ljava/io/FileNotFoundException;-><init>(Ljava/lang/String;)V
throw v0
.end method
# virtual methods
.method public doSomething1(I)V
.registers 3
.param p1, "i" # I
.line 20
const/4 v0, 0x1
if-ne p1, v0, :cond_7
.line 21
invoke-virtual {p0, p1}, Ljadx/test/MissingThrowsTest;->doSomething2(I)V
goto :goto_a
.line 23
:cond_7
invoke-virtual {p0, p1}, Ljadx/test/MissingThrowsTest;->doSomething1(I)V
.line 25
:goto_a
return-void
.end method
.method public doSomething2(I)V
.registers 3
.param p1, "i" # I
.line 28
const/4 v0, 0x1
if-ne p1, v0, :cond_7
.line 29
invoke-direct {p0}, Ljadx/test/MissingThrowsTest;->exceptionSource()V
goto :goto_a
.line 31
:cond_7
invoke-virtual {p0, p1}, Ljadx/test/MissingThrowsTest;->doSomething1(I)V
.line 33
:goto_a
return-void
.end method
.method public mergeThrownExcetions()V
.registers 1
.annotation system Ldalvik/annotation/Throws;
value = {
Ljava/io/IOException;
}
.end annotation
.line 12
invoke-direct {p0}, Ljadx/test/MissingThrowsTest;->exceptionSource()V
.line 13
return-void
.end method
.method public missingThrowsAnnotation()V
.registers 1
.line 16
invoke-direct {p0}, Ljadx/test/MissingThrowsTest;->exceptionSource()V
.line 17
return-void
.end method
Actual result:
package jadx.test;
import java.io.FileNotFoundException;
import java.io.IOException;
/* loaded from: MissingThrowsTest.smali */
public class MissingThrowsTest {
private void exceptionSource() { // Unhandled exception: java. io. FileNotFoundException
throw new FileNotFoundException("");
}
public void mergeThrownExcetions() throws IOException {
exceptionSource();
}
public void missingThrowsAnnotation() { // Unhandled exception: java. io. FileNotFoundException
exceptionSource();
}
public void doSomething1(int i) { // Unhandled exception: java. io. FileNotFoundException
if (i == 1) {
doSomething2(i);
} else {
doSomething1(i);
}
}
public void doSomething2(int i) { // Unhandled exception: java. io. FileNotFoundException
if (i == 1) {
exceptionSource();
} else {
doSomething1(i);
}
}
private void invalidException() throws String { // error: incompatible types: String cannot be converted to Throwable + Unhandled exception: java. io. FileNotFoundException
throw new FileNotFoundException("");
}
}Expected result:
package jadx.test;
import java.io.FileNotFoundException;
import java.io.IOException;
/* loaded from: MissingThrowsTest.smali */
public class MissingThrowsTest {
private void exceptionSource() throws FileNotFoundException {
throw new FileNotFoundException("");
}
public void mergeThrownExcetions() throws IOException {
exceptionSource();
}
public void missingThrowsAnnotation() throws FileNotFoundException {
exceptionSource();
}
public void doSomething1(int i) throws FileNotFoundException {
if (i == 1) {
doSomething2(i);
} else {
doSomething1(i);
}
}
public void doSomething2(int i) throws FileNotFoundException {
if (i == 1) {
exceptionSource();
} else {
doSomething1(i);
}
}
// JADX WARN: Skipped invalid throws annotation
private void invalidException() throws FileNotFoundException {
throw new FileNotFoundException("");
}
}Relevant log output or stacktrace
Provide sample and class/method full name
Real world example:
https://spider-solitaire-freecell.apk.gold/
Class com.startapp.android.publish.gson.stream.JsonReader
private IOException syntaxError(String message)
Jadx version
latest git master