Issue details
jadx takes a nested class's static modifier straight from the InnerClass attribute and never re-infers it.
R8 can strip the STATIC flag from a class that is in fact static (its constructor takes no synthetic outer-this argument).
The class is then emitted non-static, so a new Outer.Inner() from a static context does not compile.
The information to recover it is already in the bytecode: a non-static inner class's constructor has a synthetic first argument of the outer type (this is exactly the check in MethodNode.java#L535-L540).
A member class whose constructors have no such argument is static.
The subtlety is that this must apply only to member classes: local and anonymous classes also lack the outer-this arg but cannot be static.
A naive add-only inference over any inner class with no outer-this ctor wrongly marks a non-inlined $1-style or local class static (it regresses TestIncorrectAnonymousClass), and there is no enclosing-method info to separate member classes from those, so this needs a signal beyond the class name.
Sample
Outer.smali (member class a with a static factory):
.class public La;
.super Ljava/lang/Object;
.annotation system Ldalvik/annotation/MemberClasses;
value = {
La$b;
}
.end annotation
.method public static make()La$b;
.registers 1
new-instance v0, La$b;
invoke-direct {v0}, La$b;-><init>()V
return-object v0
.end method
Inner.smali (static in fact -- no outer-this ctor arg -- but the InnerClass STATIC flag is absent):
.class public La$b;
.super Ljava/lang/Object;
.annotation system Ldalvik/annotation/EnclosingClass;
value = La;
.end annotation
.annotation system Ldalvik/annotation/InnerClass;
accessFlags = 0x1
name = "b"
.end annotation
.method public constructor <init>()V
.registers 1
invoke-direct {p0}, Ljava/lang/Object;-><init>()V
return-void
.end method
Output on master (8c28a85), does not compile (new a.b() in a static method needs an enclosing a instance):
public class a {
public class b {
}
public static a.b make() {
return new a.b();
}
}
Jadx version
master 8c28a85 (current HEAD)
Issue details
jadx takes a nested class's
staticmodifier straight from the InnerClass attribute and never re-infers it.R8 can strip the STATIC flag from a class that is in fact static (its constructor takes no synthetic outer-
thisargument).The class is then emitted non-static, so a
new Outer.Inner()from a static context does not compile.The information to recover it is already in the bytecode: a non-static inner class's constructor has a synthetic first argument of the outer type (this is exactly the check in MethodNode.java#L535-L540).
A member class whose constructors have no such argument is static.
The subtlety is that this must apply only to member classes: local and anonymous classes also lack the outer-
thisarg but cannot bestatic.A naive add-only inference over any inner class with no outer-
thisctor wrongly marks a non-inlined$1-style or local classstatic(it regressesTestIncorrectAnonymousClass), and there is no enclosing-method info to separate member classes from those, so this needs a signal beyond the class name.Sample
Outer.smali(member classawith a static factory):Inner.smali(static in fact -- no outer-thisctor arg -- but the InnerClass STATIC flag is absent):Output on master (
8c28a85), does not compile (new a.b()in a static method needs an enclosingainstance):Jadx version
master
8c28a85(current HEAD)