Skip to content

Commit

Permalink
Add support for <clinit> in ConfigurationParser
Browse files Browse the repository at this point in the history
  • Loading branch information
bengt-GS authored and Oberon Swings committed Nov 17, 2023
1 parent e76e479 commit 789777d
Show file tree
Hide file tree
Showing 4 changed files with 95 additions and 11 deletions.
35 changes: 24 additions & 11 deletions base/src/main/java/proguard/ConfigurationParser.java
Original file line number Diff line number Diff line change
Expand Up @@ -1183,24 +1183,29 @@ else if (isMethods)
// Did we get just one word before the opening parenthesis?
if (ConfigurationConstants.OPEN_ARGUMENTS_KEYWORD.equals(name))
{
// This must be a constructor then.
// Make sure the type is a proper constructor name.
if (!(type.equals(ClassConstants.METHOD_NAME_INIT) ||
type.equals(externalClassName) ||
type.equals(ClassUtil.externalShortClassName(externalClassName))))
// This must be an initializer then.
// Make sure the type is a proper initializer name.
if (ClassUtil.isInitializer(type))
{
name = type; // This is either `<init>` or `<clinit>`.
type = JavaTypeConstants.VOID;
}
else if (type.equals(externalClassName) ||
type.equals(ClassUtil.externalShortClassName(externalClassName)))
{
name = ClassConstants.METHOD_NAME_INIT;
type = JavaTypeConstants.VOID;
}
else
{
throw new ParseException("Expecting type and name " +
"instead of just '" + type +
"' before " + reader.locationDescription());
}

// Assign the fixed constructor type and name.
type = JavaTypeConstants.VOID;
name = ClassConstants.METHOD_NAME_INIT;
}
else
{
// It's not a constructor.
// It's not an initializer.
// Make sure we have a proper name.
checkNextWordIsJavaIdentifier("class member name");

Expand All @@ -1211,7 +1216,7 @@ else if (isMethods)
}

// Check if the type actually contains the use of generics.
// Can not do it right away as we also support "<init>" as a type (see case above).
// Can not do it right away as we also support "<init>" and "<clinit>" as a type (see case above).
if (containsGenerics(type))
{
throw new ParseException("Generics are not allowed (erased) for java type" + typeLocation);
Expand Down Expand Up @@ -1292,6 +1297,14 @@ else if (ConfigurationConstants.OPEN_ARGUMENTS_KEYWORD.equals(nextWord))
"' before " + reader.locationDescription());
}

// Class initializers are not supposed to have any parameters.
if (ClassConstants.METHOD_NAME_CLINIT.equals(name) &&
ClassUtil.internalMethodParameterCount(descriptor) > 0)
{
throw new ParseException("Not expecting method parameters with initializer '" + ClassConstants.METHOD_NAME_CLINIT +
"' before " + reader.locationDescription());
}

// Read the separator after the closing parenthesis.
readNextWord("separator '" + ConfigurationConstants.SEPARATOR_KEYWORD + "'");

Expand Down
25 changes: 25 additions & 0 deletions base/src/test/kotlin/proguard/ConfigurationParserTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,31 @@ class ConfigurationParserTest : FreeSpec({
parseConfiguration("-keep class * { public protected <methods>; }")
}

"Keep rule with ClassName should be valid" {
val configuration = parseConfiguration("-keep class ClassName { ClassName(); }")
val keep = configuration.keep.single().methodSpecifications.single()
keep.name shouldBe "<init>"
keep.descriptor shouldBe "()V"
}

"Keep rule with ClassName and external class com.example.ClassName should be valid" {
val configuration = parseConfiguration("-keep class com.example.ClassName { ClassName(); }")
val keep = configuration.keep.single().methodSpecifications.single()
keep.name shouldBe "<init>"
keep.descriptor shouldBe "()V"
}

"Keep rule with <clinit> should be valid" {
val configuration = parseConfiguration("-keep class ** { <clinit>(); }")
val keep = configuration.keep.single().methodSpecifications.single()
keep.name shouldBe "<clinit>"
keep.descriptor shouldBe "()V"
}

"Keep rule with <clinit> and non-empty argument list should throw ParseException" {
shouldThrow<ParseException> { parseConfiguration("-keep class * { void <clinit>(int) }") }
}

"Keep rule with * member wildcard and return type should be valid" {
parseConfiguration("-keep class * { java.lang.String *; }")
}
Expand Down
42 changes: 42 additions & 0 deletions base/src/test/kotlin/proguard/ConfigurationWriterTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,48 @@ class ConfigurationWriterTest : FreeSpec({
return out.toString().trim()
}

"Keep rules tests" - {
"Keep class constructor should be kept" {
val rules = """
-keep class * {
<init>();
}
""".trimIndent()
val out = printConfiguration(rules)
out shouldBe rules
}

"Keep class initializer should be kept" {
val rules = """
-keep class * {
<clinit>();
}
""".trimIndent()
val out = printConfiguration(rules)
val expected = """
-keep class * {
void <clinit>();
}
""".trimIndent()
out shouldBe expected
}

"Keep class initializer should respect allowobfuscation flag" {
val rules = """
-keep,allowobfuscation class ** extends com.example.A {
<clinit>();
}
""".trimIndent()
val out = printConfiguration(rules)
val expected = """
-keep,allowobfuscation class ** extends com.example.A {
void <clinit>();
}
""".trimIndent()
out shouldBe expected
}
}

"Hash character handling tests" - {
"Option parameters with hash characters should be quoted" {
printConfiguration("-keystorepassword '#tester'") shouldBe "-keystorepassword '#tester'"
Expand Down
4 changes: 4 additions & 0 deletions docs/md/manual/releasenotes.md