Skip to content

Commit 36da850

Browse files
Fix inner class access flags (#20)
1 parent a923540 commit 36da850

3 files changed

Lines changed: 271 additions & 1 deletion

File tree

pga-lib/src/main/java/com/guardsquare/proguard/assembler/AttributesParser.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -437,7 +437,7 @@ public void visitBootstrapMethodInfo(Clazz clazz, BootstrapMethodInfo bootstrapM
437437

438438
public void visitInnerClassesInfo(Clazz clazz, InnerClassesInfo innerClassesInfo)
439439
{
440-
innerClassesInfo.u2innerClassAccessFlags = p.expectClassAccessFlags();
440+
innerClassesInfo.u2innerClassAccessFlags = p.expectInnerClassAccessFlags();
441441
innerClassesInfo.u2innerClassIndex =
442442
cpe.addClassConstant(ClassUtil.internalClassName(p.expectWord("inner class")), null);
443443
while (p.expectIfNextTtypeEqualsWord(AssemblyConstants.AS,

pga-lib/src/main/java/com/guardsquare/proguard/assembler/Parser.java

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -549,6 +549,65 @@ public int expectClassAccessFlags()
549549
}
550550

551551

552+
/**
553+
* Parses one or more inner class access flags, as defined in JVMS §4.7.6.
554+
*
555+
* @return the access flags, in bit vector format.
556+
*/
557+
public int expectInnerClassAccessFlags()
558+
{
559+
int accessFlags = 0;
560+
while (nextTtypeEqualsWord())
561+
{
562+
switch (sval)
563+
{
564+
case JavaAccessConstants.PUBLIC: accessFlags |= AccessConstants.PUBLIC; break;
565+
case JavaAccessConstants.PRIVATE: accessFlags |= AccessConstants.PRIVATE; break;
566+
case JavaAccessConstants.PROTECTED: accessFlags |= AccessConstants.PROTECTED; break;
567+
case JavaAccessConstants.STATIC: accessFlags |= AccessConstants.STATIC; break;
568+
case JavaAccessConstants.FINAL: accessFlags |= AccessConstants.FINAL; break;
569+
case JavaAccessConstants.ABSTRACT: accessFlags |= AccessConstants.ABSTRACT; break;
570+
case JavaAccessConstants.SYNTHETIC: accessFlags |= AccessConstants.SYNTHETIC; break;
571+
default:
572+
{
573+
// Syntactic sugar: enum notation for enums.
574+
if (JavaAccessConstants.ENUM.equals(sval))
575+
{
576+
return accessFlags |
577+
AccessConstants.ENUM;
578+
}
579+
580+
if (JavaAccessConstants.INTERFACE.equals(sval))
581+
{
582+
// Syntactic sugar: adding ACC_ABSTRACT to interfaces automatically.
583+
return accessFlags |
584+
AccessConstants.INTERFACE |
585+
AccessConstants.ABSTRACT;
586+
}
587+
588+
if (AssemblyConstants.CLASS.equals(sval))
589+
{
590+
return accessFlags;
591+
}
592+
593+
throwKeywordError(JavaAccessConstants.ENUM,
594+
JavaAccessConstants.INTERFACE,
595+
AssemblyConstants.CLASS);
596+
}
597+
}
598+
}
599+
600+
// Syntactic sugar: @interface notation for annotations.
601+
expect(ElementValue.TAG_ANNOTATION, "annotation class");
602+
expectWord(JavaAccessConstants.INTERFACE);
603+
// Syntactic sugar: adding ACC_ABSTRACT to interfaces automatically.
604+
return accessFlags |
605+
AccessConstants.INTERFACE |
606+
AccessConstants.ABSTRACT |
607+
AccessConstants.ANNOTATION;
608+
}
609+
610+
552611
/**
553612
* Parses a loadable constant and returns its index in the constant pool.
554613
*
Lines changed: 211 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,211 @@
1+
package com.guardsquare.proguard
2+
3+
import com.guardsquare.proguard.assembler.ClassParser
4+
import com.guardsquare.proguard.assembler.Parser
5+
import com.guardsquare.proguard.disassembler.ClassPrinter
6+
import com.guardsquare.proguard.disassembler.Printer
7+
import io.kotest.core.spec.style.FreeSpec
8+
import io.kotest.matchers.shouldBe
9+
import proguard.classfile.AccessConstants
10+
import proguard.classfile.AccessConstants.ABSTRACT
11+
import proguard.classfile.AccessConstants.ANNOTATION
12+
import proguard.classfile.AccessConstants.FINAL
13+
import proguard.classfile.AccessConstants.INTERFACE
14+
import proguard.classfile.AccessConstants.PRIVATE
15+
import proguard.classfile.AccessConstants.PROTECTED
16+
import proguard.classfile.AccessConstants.PUBLIC
17+
import proguard.classfile.Clazz
18+
import proguard.classfile.ProgramClass
19+
import proguard.classfile.attribute.Attribute
20+
import proguard.classfile.attribute.InnerClassesAttribute
21+
import proguard.classfile.attribute.visitor.AttributeVisitor
22+
import java.io.PrintWriter
23+
import java.io.StringReader
24+
import java.io.StringWriter
25+
26+
/**
27+
* Tests for parsing inner class access flags in the InnerClasses attribute.
28+
*/
29+
class InnerClassAccessFlagsTest : FreeSpec({
30+
31+
"public inner class" {
32+
val jbc = """
33+
public class Outer extends java.lang.Object [
34+
InnerClasses {
35+
public class Outer${'$'}Inner as Inner in Outer;
36+
}
37+
] {
38+
}
39+
"""
40+
41+
val programClass = jbc.parseJbcToProgramClass()
42+
val flags = programClass.getInnerClassFlags(0)
43+
44+
flags shouldBe PUBLIC
45+
}
46+
47+
"public static inner class" {
48+
val jbc = """
49+
public class Outer extends java.lang.Object [
50+
InnerClasses {
51+
public static class Outer${'$'}StaticInner as StaticInner in Outer;
52+
}
53+
] {
54+
}
55+
"""
56+
57+
val programClass = jbc.parseJbcToProgramClass()
58+
val flags = programClass.getInnerClassFlags(0)
59+
60+
flags shouldBe (PUBLIC or AccessConstants.STATIC)
61+
}
62+
63+
"private inner class" {
64+
val jbc = """
65+
public class Outer extends java.lang.Object [
66+
InnerClasses {
67+
private class Outer${'$'}PrivateInner as PrivateInner in Outer;
68+
}
69+
] {
70+
}
71+
"""
72+
73+
val programClass = jbc.parseJbcToProgramClass()
74+
val flags = programClass.getInnerClassFlags(0)
75+
76+
flags shouldBe PRIVATE
77+
}
78+
79+
"public inner interface" {
80+
val jbc = """
81+
public class Outer extends java.lang.Object [
82+
InnerClasses {
83+
public interface Outer${'$'}InnerInterface as InnerInterface in Outer;
84+
}
85+
] {
86+
}
87+
"""
88+
89+
val programClass = jbc.parseJbcToProgramClass()
90+
val flags = programClass.getInnerClassFlags(0)
91+
92+
flags shouldBe (PUBLIC or INTERFACE or ABSTRACT)
93+
}
94+
95+
"public inner enum" {
96+
val jbc = """
97+
public class Outer extends java.lang.Object [
98+
InnerClasses {
99+
public enum Outer${'$'}InnerEnum as InnerEnum in Outer;
100+
}
101+
] {
102+
}
103+
"""
104+
105+
val programClass = jbc.parseJbcToProgramClass()
106+
val flags = programClass.getInnerClassFlags(0)
107+
108+
flags shouldBe (PUBLIC or AccessConstants.ENUM)
109+
}
110+
111+
"public inner annotation" {
112+
val jbc = """
113+
public class Outer extends java.lang.Object [
114+
InnerClasses {
115+
public @interface Outer${'$'}InnerAnnotation as InnerAnnotation in Outer;
116+
}
117+
] {
118+
}
119+
"""
120+
121+
val programClass = jbc.parseJbcToProgramClass()
122+
val flags = programClass.getInnerClassFlags(0)
123+
124+
flags shouldBe (PUBLIC or ANNOTATION or INTERFACE or ABSTRACT)
125+
}
126+
127+
"round-trip preserves inner class flags" {
128+
val originalJbc = """
129+
public class Outer extends java.lang.Object [
130+
InnerClasses {
131+
public class Outer${'$'}Inner as Inner in Outer;
132+
public static class Outer${'$'}StaticInner as StaticInner in Outer;
133+
private class Outer${'$'}PrivateInner as PrivateInner in Outer;
134+
}
135+
] {
136+
}
137+
""".trimIndent()
138+
139+
val programClass = originalJbc.parseJbcToProgramClass()
140+
val originalFlags = programClass.getAllInnerClassFlags()
141+
142+
val stringWriter = StringWriter()
143+
val printer = Printer(PrintWriter(stringWriter))
144+
val classPrinter = ClassPrinter(printer)
145+
programClass.accept(classPrinter)
146+
147+
val reparsedClass = stringWriter.toString().parseJbcToProgramClass()
148+
val reparsedFlags = reparsedClass.getAllInnerClassFlags()
149+
150+
reparsedFlags shouldBe originalFlags
151+
}
152+
153+
"multiple inner classes with different access flags" {
154+
val jbc = """
155+
public class Outer extends java.lang.Object [
156+
InnerClasses {
157+
public class Outer${'$'}PublicInner as PublicInner in Outer;
158+
protected class Outer${'$'}ProtectedInner as ProtectedInner in Outer;
159+
class Outer${'$'}PackageInner as PackageInner in Outer;
160+
private final class Outer${'$'}PrivateFinalInner as PrivateFinalInner in Outer;
161+
public abstract class Outer${'$'}AbstractInner as AbstractInner in Outer;
162+
}
163+
] {
164+
}
165+
"""
166+
167+
val programClass = jbc.parseJbcToProgramClass()
168+
169+
programClass.getInnerClassFlags(0) shouldBe PUBLIC
170+
programClass.getInnerClassFlags(1) shouldBe PROTECTED
171+
programClass.getInnerClassFlags(2) shouldBe 0
172+
programClass.getInnerClassFlags(3) shouldBe (PRIVATE or FINAL)
173+
programClass.getInnerClassFlags(4) shouldBe (PUBLIC or ABSTRACT)
174+
}
175+
})
176+
177+
private fun String.parseJbcToProgramClass(): ProgramClass {
178+
val programClass = ProgramClass()
179+
programClass.accept(
180+
ClassParser(
181+
Parser(
182+
StringReader(this),
183+
),
184+
),
185+
)
186+
return programClass
187+
}
188+
189+
private fun ProgramClass.getInnerClassFlags(index: Int): Int {
190+
var flags = -1
191+
attributesAccept(object : AttributeVisitor {
192+
override fun visitAnyAttribute(clazz: Clazz, attribute: Attribute) {}
193+
override fun visitInnerClassesAttribute(clazz: Clazz, attr: InnerClassesAttribute) {
194+
flags = attr.classes[index].u2innerClassAccessFlags
195+
}
196+
})
197+
return flags
198+
}
199+
200+
private fun ProgramClass.getAllInnerClassFlags(): List<Int> {
201+
val flagsList = mutableListOf<Int>()
202+
attributesAccept(object : AttributeVisitor {
203+
override fun visitAnyAttribute(clazz: Clazz, attribute: Attribute) {}
204+
override fun visitInnerClassesAttribute(clazz: Clazz, attr: InnerClassesAttribute) {
205+
for (i in 0 until attr.u2classesCount) {
206+
flagsList.add(attr.classes[i].u2innerClassAccessFlags)
207+
}
208+
}
209+
})
210+
return flagsList
211+
}

0 commit comments

Comments
 (0)